From 16d34bcc0a04b319a53d86f20a863a837eb8d73d Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 12 Apr 2021 16:02:19 -0400 Subject: [PATCH 001/400] Expose the latest end time of storyboard elements Co-authored-by: Marlina Bowring --- osu.Game/Storyboards/IStoryboardElement.cs | 2 ++ osu.Game/Storyboards/Storyboard.cs | 9 +++++++++ osu.Game/Storyboards/StoryboardSample.cs | 2 ++ osu.Game/Storyboards/StoryboardVideo.cs | 2 ++ 4 files changed, 15 insertions(+) diff --git a/osu.Game/Storyboards/IStoryboardElement.cs b/osu.Game/Storyboards/IStoryboardElement.cs index c4c150a8a4..03f8b97212 100644 --- a/osu.Game/Storyboards/IStoryboardElement.cs +++ b/osu.Game/Storyboards/IStoryboardElement.cs @@ -12,6 +12,8 @@ namespace osu.Game.Storyboards double StartTime { get; } + double EndTime { get; } + Drawable CreateDrawable(); } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 1ba25cc11e..41058d9cb3 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -36,6 +36,15 @@ namespace osu.Game.Storyboards /// public double? EarliestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.StartTime).FirstOrDefault()?.StartTime; + /// + /// Across all layers, find the latest point in time that a storyboard element ends at. + /// Will return null if there are no elements. + /// + /// + /// This iterates all elements and as such should be used sparingly or stored locally. + /// Videos and samples return StartTime as their EndTIme. + /// + public double? LatestEventTime => Layers.SelectMany(l => l.Elements).OrderByDescending(e => e.EndTime).FirstOrDefault()?.EndTime; /// /// Depth of the currently front-most storyboard layer, excluding the overlay layer. /// diff --git a/osu.Game/Storyboards/StoryboardSample.cs b/osu.Game/Storyboards/StoryboardSample.cs index 5d6ce215f5..d0949c93a7 100644 --- a/osu.Game/Storyboards/StoryboardSample.cs +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -15,6 +15,8 @@ namespace osu.Game.Storyboards public double StartTime { get; } + public double EndTime => StartTime; + public int Volume { get; } public IEnumerable LookupNames => new[] diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs index 4652e45852..1314bd7cb9 100644 --- a/osu.Game/Storyboards/StoryboardVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -14,6 +14,8 @@ namespace osu.Game.Storyboards public double StartTime { get; } + public double EndTime => StartTime; + public StoryboardVideo(string path, int offset) { Path = path; From 1aa36818df11577979863233dd0a149de582504b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 17:47:11 +0900 Subject: [PATCH 002/400] Abstractify GameplayClockContainer --- .../TestSceneSpinnerRotation.cs | 3 +- .../TestSceneGameplayClockContainer.cs | 2 +- .../Gameplay/TestSceneStoryboardSamples.cs | 4 +- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 4 +- .../Screens/Play/GameplayClockContainer.cs | 277 +++++++++--------- osu.Game/Screens/Play/Player.cs | 11 +- osu.Game/Screens/Play/SkipOverlay.cs | 2 +- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 +- 8 files changed, 156 insertions(+), 149 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 14c709cae1..8ff21057b5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Storyboards; using osu.Game.Tests.Visual; using osuTK; @@ -193,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(0); - AddStep("adjust track rate", () => Player.GameplayClockContainer.UserPlaybackRate.Value = rate); + AddStep("adjust track rate", () => ((MasterGameplayClockContainer)Player.GameplayClockContainer).UserPlaybackRate.Value = rate); addSeekStep(1000); AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05)); diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs index 891537c4ad..4d5dcabbba 100644 --- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gcc = new GameplayClockContainer(working, 0)); + Add(gcc = new MasterGameplayClockContainer(working, 0)); }); AddStep("start track", () => gcc.Start()); diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index cae5f20332..7394458482 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayContainer = new GameplayClockContainer(working, 0)); + Add(gameplayContainer = new MasterGameplayClockContainer(working, 0)); gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) { @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Gameplay var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); - Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0) + Add(gameplayContainer = new MasterGameplayClockContainer(Beatmap.Value, 0) { Child = beatmapSkinSourceContainer }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 841722a8f1..e08e03b789 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); working.LoadTrack(); - Child = gameplayClockContainer = new GameplayClockContainer(working, 0) + Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("click", () => { - increment = skip_time - gameplayClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2; + increment = skip_time - gameplayClock.CurrentTime - MasterGameplayClockContainer.MINIMUM_SKIP_TIME / 2; InputManager.Click(MouseButton.Left); }); AddStep("click", () => InputManager.Click(MouseButton.Left)); diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ddbb087962..163ed9444f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -19,27 +17,90 @@ using osu.Game.Configuration; namespace osu.Game.Screens.Play { - /// - /// Encapsulates gameplay timing logic and provides a for children. - /// - public class GameplayClockContainer : Container + public abstract class GameplayClockContainer : Container { - private readonly WorkingBeatmap beatmap; - - [NotNull] - private ITrack track; + /// + /// The final clock which is exposed to underlying components. + /// + public GameplayClock GameplayClock { get; private set; } public readonly BindableBool IsPaused = new BindableBool(); /// /// The decoupled clock used for gameplay. Should be used for seeks and clock control. /// - private readonly DecoupleableInterpolatingFramedClock adjustableClock; + protected readonly DecoupleableInterpolatingFramedClock AdjustableClock; - private readonly double gameplayStartTime; - private readonly bool startAtGameplayStart; + protected readonly IClock SourceClock; - private readonly double firstHitObjectTime; + protected GameplayClockContainer(IClock sourceClock) + { + SourceClock = sourceClock; + + RelativeSizeAxes = Axes.Both; + + AdjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + AdjustableClock.ChangeSource(SourceClock); + + IsPaused.BindValueChanged(OnPauseChanged); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(GameplayClock = CreateGameplayClock(AdjustableClock)); + + return dependencies; + } + + public virtual void Start() + { + if (!AdjustableClock.IsRunning) + { + // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time + // This accounts for the clock source potentially taking time to enter a completely stopped state + Seek(GameplayClock.CurrentTime); + + AdjustableClock.Start(); + } + + IsPaused.Value = false; + } + + /// + /// Seek to a specific time in gameplay. + /// + /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). + /// + /// + /// The destination time to seek to. + public virtual void Seek(double time) => AdjustableClock.Seek(time); + + public virtual void Stop() => IsPaused.Value = true; + + public virtual void Restart() + { + AdjustableClock.Seek(0); + AdjustableClock.Stop(); + + if (!IsPaused.Value) + Start(); + } + + protected abstract void OnPauseChanged(ValueChangedEvent isPaused); + + protected abstract GameplayClock CreateGameplayClock(IClock source); + } + + public class MasterGameplayClockContainer : GameplayClockContainer + { + /// + /// Duration before gameplay start time required before skip button displays. + /// + public const double MINIMUM_SKIP_TIME = 1000; + + protected new DecoupleableInterpolatingFramedClock SourceClock => (DecoupleableInterpolatingFramedClock)base.SourceClock; public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { @@ -49,73 +110,32 @@ namespace osu.Game.Screens.Play Precision = 0.1, }; - /// - /// The final clock which is exposed to underlying components. - /// - public GameplayClock GameplayClock => localGameplayClock; + private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; - [Cached(typeof(GameplayClock))] - private readonly LocalGameplayClock localGameplayClock; + private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); + private readonly WorkingBeatmap beatmap; + private readonly double gameplayStartTime; + private readonly bool startAtGameplayStart; + private readonly double firstHitObjectTime; + + private FramedOffsetClock userOffsetClock; + private FramedOffsetClock platformOffsetClock; + private LocalGameplayClock localGameplayClock; private Bindable userAudioOffset; - private readonly FramedOffsetClock userOffsetClock; - - private readonly FramedOffsetClock platformOffsetClock; - - /// - /// Creates a new . - /// - /// The beatmap being played. - /// The suggested time to start gameplay at. - /// - /// Whether should be used regardless of when storyboard events and hitobjects are supposed to start. - /// - public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) + public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) + : base(new DecoupleableInterpolatingFramedClock()) { this.beatmap = beatmap; this.gameplayStartTime = gameplayStartTime; this.startAtGameplayStart = startAtGameplayStart; - track = beatmap.Track; firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; - RelativeSizeAxes = Axes.Both; - - adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - - // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. - // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - platformOffsetClock = new HardwareCorrectionOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; - - // the final usable gameplay clock with user-set offsets applied. - userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); - - // the clock to be exposed via DI to children. - localGameplayClock = new LocalGameplayClock(userOffsetClock); - - GameplayClock.IsPaused.BindTo(IsPaused); - - IsPaused.BindValueChanged(onPauseChanged); + SourceClock.ChangeSource(beatmap.Track); } - private void onPauseChanged(ValueChangedEvent isPaused) - { - if (isPaused.NewValue) - this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => adjustableClock.Stop()); - else - this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); - } - - private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; - - /// - /// Duration before gameplay start time required before skip button displays. - /// - public const double MINIMUM_SKIP_TIME = 1000; - - private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -143,39 +163,31 @@ namespace osu.Game.Screens.Play Seek(startTime); - adjustableClock.ProcessFrame(); + AdjustableClock.ProcessFrame(); } - public void Restart() + protected override void OnPauseChanged(ValueChangedEvent isPaused) { - Task.Run(() => - { - track.Seek(0); - track.Stop(); - - Schedule(() => - { - adjustableClock.ChangeSource(track); - updateRate(); - - if (!IsPaused.Value) - Start(); - }); - }); + if (isPaused.NewValue) + this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableClock.Stop()); + else + this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } - public void Start() + public override void Seek(double time) { - if (!adjustableClock.IsRunning) - { - // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time - // This accounts for the audio clock source potentially taking time to enter a completely stopped state - Seek(GameplayClock.CurrentTime); + // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. + // we may want to consider reversing the application of offsets in the future as it may feel more correct. + base.Seek(time - totalOffset); - adjustableClock.Start(); - } + // manually process frame to ensure GameplayClock is correctly updated after a seek. + userOffsetClock.ProcessFrame(); + } - IsPaused.Value = false; + public override void Restart() + { + updateRate(); + base.Restart(); } /// @@ -195,26 +207,24 @@ namespace osu.Game.Screens.Play Seek(skipTarget); } - /// - /// Seek to a specific time in gameplay. - /// - /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). - /// - /// - /// The destination time to seek to. - public void Seek(double time) + protected override GameplayClock CreateGameplayClock(IClock source) { - // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. - // we may want to consider reversing the application of offsets in the future as it may feel more correct. - adjustableClock.Seek(time - totalOffset); + // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. + // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. + platformOffsetClock = new HardwareCorrectionOffsetClock(source) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; - // manually process frame to ensure GameplayClock is correctly updated after a seek. - userOffsetClock.ProcessFrame(); + // the final usable gameplay clock with user-set offsets applied. + userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); + + return localGameplayClock = new LocalGameplayClock(userOffsetClock); } - public void Stop() + protected override void Update() { - IsPaused.Value = true; + if (!IsPaused.Value) + userOffsetClock.ProcessFrame(); + + base.Update(); } /// @@ -223,19 +233,7 @@ namespace osu.Game.Screens.Play public void StopUsingBeatmapClock() { removeSourceClockAdjustments(); - - track = new TrackVirtual(track.Length); - adjustableClock.ChangeSource(track); - } - - protected override void Update() - { - if (!IsPaused.Value) - { - userOffsetClock.ProcessFrame(); - } - - base.Update(); + SourceClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); } private bool speedAdjustmentsApplied; @@ -245,6 +243,8 @@ namespace osu.Game.Screens.Play if (speedAdjustmentsApplied) return; + var track = (Track)SourceClock.Source; + track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -254,15 +254,12 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = true; } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - removeSourceClockAdjustments(); - } - private void removeSourceClockAdjustments() { - if (!speedAdjustmentsApplied) return; + if (!speedAdjustmentsApplied) + return; + + var track = (Track)SourceClock.Source; track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -273,16 +270,10 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = false; } - private class LocalGameplayClock : GameplayClock + protected override void Dispose(bool isDisposing) { - public readonly List> MutableNonGameplayAdjustments = new List>(); - - public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; - - public LocalGameplayClock(FramedOffsetClock underlyingClock) - : base(underlyingClock) - { - } + base.Dispose(isDisposing); + removeSourceClockAdjustments(); } private class HardwareCorrectionOffsetClock : FramedOffsetClock @@ -296,5 +287,17 @@ namespace osu.Game.Screens.Play { } } + + private class LocalGameplayClock : GameplayClock + { + public readonly List> MutableNonGameplayAdjustments = new List>(); + + public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + + public LocalGameplayClock(FramedOffsetClock underlyingClock) + : base(underlyingClock) + { + } + } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index efe5d26409..e07c40e8ff 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -295,7 +295,7 @@ namespace osu.Game.Screens.Play IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } - protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new GameplayClockContainer(beatmap, gameplayStart); + protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); private Drawable createUnderlayComponents() => DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; @@ -342,7 +342,6 @@ namespace osu.Game.Screens.Play Action = () => PerformExit(true), IsPaused = { BindTarget = GameplayClockContainer.IsPaused } }, - PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, KeyCounter = { AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, @@ -386,6 +385,9 @@ namespace osu.Game.Screens.Play } }; + if (GameplayClockContainer is MasterGameplayClockContainer master) + HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.BindTarget = master.UserPlaybackRate; + if (!Configuration.AllowSkippingIntro) skipOverlay.Expire(); @@ -533,7 +535,8 @@ namespace osu.Game.Screens.Play // user requested skip // disable sample playback to stop currently playing samples and perform skip samplePlaybackDisabled.Value = true; - GameplayClockContainer.Skip(); + + (GameplayClockContainer as MasterGameplayClockContainer)?.Skip(); // return samplePlaybackDisabled.Value to what is defined by the beatmap's current state updateSampleDisabledState(); @@ -832,7 +835,7 @@ namespace osu.Game.Screens.Play // 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(); + (GameplayClockContainer as MasterGameplayClockContainer)?.StopUsingBeatmapClock(); musicController.ResetTrackAdjustments(); diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 3f214e49d9..ddb78dfb67 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Play private const double fade_time = 300; - private double fadeOutBeginTime => startTime - GameplayClockContainer.MINIMUM_SKIP_TIME; + private double fadeOutBeginTime => startTime - MasterGameplayClockContainer.MINIMUM_SKIP_TIME; protected override void LoadComplete() { diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index fdf996150f..9822f62dd8 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Play if (firstFrameTime == null || firstFrameTime <= gameplayStart + 5000) return base.CreateGameplayClockContainer(beatmap, gameplayStart); - return new GameplayClockContainer(beatmap, firstFrameTime.Value, true); + return new MasterGameplayClockContainer(beatmap, firstFrameTime.Value, true); } public override bool OnExiting(IScreen next) From 2935f87e7082bd603c9a09aecda6ba51628a184b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 18:29:34 +0900 Subject: [PATCH 003/400] Fix IsPaused not being bound --- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 163ed9444f..a97a87d73f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -50,6 +50,7 @@ namespace osu.Game.Screens.Play var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(GameplayClock = CreateGameplayClock(AdjustableClock)); + GameplayClock.IsPaused.BindTo(IsPaused); return dependencies; } From b53b30c1a97a71822359016e87d1334ce8097476 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 19:32:48 +0900 Subject: [PATCH 004/400] Fix incorrect offset due to another intermediate Decoupleable clock --- .../Screens/Play/GameplayClockContainer.cs | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index a97a87d73f..36ca7415d0 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -31,16 +31,12 @@ namespace osu.Game.Screens.Play /// protected readonly DecoupleableInterpolatingFramedClock AdjustableClock; - protected readonly IClock SourceClock; - protected GameplayClockContainer(IClock sourceClock) { - SourceClock = sourceClock; - RelativeSizeAxes = Axes.Both; AdjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - AdjustableClock.ChangeSource(SourceClock); + AdjustableClock.ChangeSource(sourceClock); IsPaused.BindValueChanged(OnPauseChanged); } @@ -101,7 +97,7 @@ namespace osu.Game.Screens.Play /// public const double MINIMUM_SKIP_TIME = 1000; - protected new DecoupleableInterpolatingFramedClock SourceClock => (DecoupleableInterpolatingFramedClock)base.SourceClock; + protected Track Track => (Track)AdjustableClock.Source; public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { @@ -126,15 +122,13 @@ namespace osu.Game.Screens.Play private Bindable userAudioOffset; public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) - : base(new DecoupleableInterpolatingFramedClock()) + : base(beatmap.Track) { this.beatmap = beatmap; this.gameplayStartTime = gameplayStartTime; this.startAtGameplayStart = startAtGameplayStart; firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; - - SourceClock.ChangeSource(beatmap.Track); } [BackgroundDependencyLoader] @@ -234,7 +228,7 @@ namespace osu.Game.Screens.Play public void StopUsingBeatmapClock() { removeSourceClockAdjustments(); - SourceClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); + AdjustableClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); } private bool speedAdjustmentsApplied; @@ -244,10 +238,8 @@ namespace osu.Game.Screens.Play if (speedAdjustmentsApplied) return; - var track = (Track)SourceClock.Source; - - track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + Track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + Track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); @@ -260,10 +252,8 @@ namespace osu.Game.Screens.Play if (!speedAdjustmentsApplied) return; - var track = (Track)SourceClock.Source; - - track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + Track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + Track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); From 18c69cdaf7bb536d22c704171ba9bd03c00689cd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 19:50:22 +0900 Subject: [PATCH 005/400] Split out files --- .../Screens/Play/GameplayClockContainer.cs | 214 +---------------- .../Play/MasterGameplayClockContainer.cs | 220 ++++++++++++++++++ 2 files changed, 222 insertions(+), 212 deletions(-) create mode 100644 osu.Game/Screens/Play/MasterGameplayClockContainer.cs diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 36ca7415d0..d8e6fda87e 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -1,19 +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; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Configuration; namespace osu.Game.Screens.Play { @@ -38,7 +30,7 @@ namespace osu.Game.Screens.Play AdjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; AdjustableClock.ChangeSource(sourceClock); - IsPaused.BindValueChanged(OnPauseChanged); + IsPaused.BindValueChanged(OnIsPausedChanged); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -85,210 +77,8 @@ namespace osu.Game.Screens.Play Start(); } - protected abstract void OnPauseChanged(ValueChangedEvent isPaused); + protected abstract void OnIsPausedChanged(ValueChangedEvent isPaused); protected abstract GameplayClock CreateGameplayClock(IClock source); } - - public class MasterGameplayClockContainer : GameplayClockContainer - { - /// - /// Duration before gameplay start time required before skip button displays. - /// - public const double MINIMUM_SKIP_TIME = 1000; - - protected Track Track => (Track)AdjustableClock.Source; - - public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) - { - Default = 1, - MinValue = 0.5, - MaxValue = 2, - Precision = 0.1, - }; - - private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; - - private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); - - private readonly WorkingBeatmap beatmap; - private readonly double gameplayStartTime; - private readonly bool startAtGameplayStart; - private readonly double firstHitObjectTime; - - private FramedOffsetClock userOffsetClock; - private FramedOffsetClock platformOffsetClock; - private LocalGameplayClock localGameplayClock; - private Bindable userAudioOffset; - - public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) - : base(beatmap.Track) - { - this.beatmap = beatmap; - this.gameplayStartTime = gameplayStartTime; - this.startAtGameplayStart = startAtGameplayStart; - - firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); - userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); - - // sane default provided by ruleset. - double startTime = gameplayStartTime; - - if (!startAtGameplayStart) - { - startTime = Math.Min(0, startTime); - - // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. - // this is commonly used to display an intro before the audio track start. - double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime; - if (firstStoryboardEvent != null) - startTime = Math.Min(startTime, firstStoryboardEvent.Value); - - // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. - // this is not available as an option in the live editor but can still be applied via .osu editing. - if (beatmap.BeatmapInfo.AudioLeadIn > 0) - startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); - } - - Seek(startTime); - - AdjustableClock.ProcessFrame(); - } - - protected override void OnPauseChanged(ValueChangedEvent isPaused) - { - if (isPaused.NewValue) - this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableClock.Stop()); - else - this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); - } - - public override void Seek(double time) - { - // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. - // we may want to consider reversing the application of offsets in the future as it may feel more correct. - base.Seek(time - totalOffset); - - // manually process frame to ensure GameplayClock is correctly updated after a seek. - userOffsetClock.ProcessFrame(); - } - - public override void Restart() - { - updateRate(); - base.Restart(); - } - - /// - /// Skip forward to the next valid skip point. - /// - public void Skip() - { - if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME) - return; - - double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME; - - if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) - // double skip exception for storyboards with very long intros - skipTarget = 0; - - Seek(skipTarget); - } - - protected override GameplayClock CreateGameplayClock(IClock source) - { - // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. - // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - platformOffsetClock = new HardwareCorrectionOffsetClock(source) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; - - // the final usable gameplay clock with user-set offsets applied. - userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); - - return localGameplayClock = new LocalGameplayClock(userOffsetClock); - } - - protected override void Update() - { - if (!IsPaused.Value) - userOffsetClock.ProcessFrame(); - - base.Update(); - } - - /// - /// Changes the backing clock to avoid using the originally provided track. - /// - public void StopUsingBeatmapClock() - { - removeSourceClockAdjustments(); - AdjustableClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); - } - - private bool speedAdjustmentsApplied; - - private void updateRate() - { - if (speedAdjustmentsApplied) - return; - - Track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - Track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - - localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); - localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); - - speedAdjustmentsApplied = true; - } - - private void removeSourceClockAdjustments() - { - if (!speedAdjustmentsApplied) - return; - - Track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - Track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - - localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); - localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); - - speedAdjustmentsApplied = false; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - removeSourceClockAdjustments(); - } - - private class HardwareCorrectionOffsetClock : FramedOffsetClock - { - // 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 * (Rate - 1); - - public HardwareCorrectionOffsetClock(IClock source, bool processSource = true) - : base(source, processSource) - { - } - } - - private class LocalGameplayClock : GameplayClock - { - public readonly List> MutableNonGameplayAdjustments = new List>(); - - public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; - - public LocalGameplayClock(FramedOffsetClock underlyingClock) - : base(underlyingClock) - { - } - } - } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs new file mode 100644 index 0000000000..efc8ca732e --- /dev/null +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -0,0 +1,220 @@ +// 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; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; + +namespace osu.Game.Screens.Play +{ + public class MasterGameplayClockContainer : GameplayClockContainer + { + /// + /// Duration before gameplay start time required before skip button displays. + /// + public const double MINIMUM_SKIP_TIME = 1000; + + protected Track Track => (Track)AdjustableClock.Source; + + public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) + { + Default = 1, + MinValue = 0.5, + MaxValue = 2, + Precision = 0.1, + }; + + private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + + private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); + + private readonly WorkingBeatmap beatmap; + private readonly double gameplayStartTime; + private readonly bool startAtGameplayStart; + private readonly double firstHitObjectTime; + + private FramedOffsetClock userOffsetClock; + private FramedOffsetClock platformOffsetClock; + private LocalGameplayClock localGameplayClock; + private Bindable userAudioOffset; + + public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) + : base(beatmap.Track) + { + this.beatmap = beatmap; + this.gameplayStartTime = gameplayStartTime; + this.startAtGameplayStart = startAtGameplayStart; + + firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); + userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); + + // sane default provided by ruleset. + double startTime = gameplayStartTime; + + if (!startAtGameplayStart) + { + startTime = Math.Min(0, startTime); + + // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. + // this is commonly used to display an intro before the audio track start. + double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime; + if (firstStoryboardEvent != null) + startTime = Math.Min(startTime, firstStoryboardEvent.Value); + + // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. + // this is not available as an option in the live editor but can still be applied via .osu editing. + if (beatmap.BeatmapInfo.AudioLeadIn > 0) + startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + } + + Seek(startTime); + + AdjustableClock.ProcessFrame(); + } + + protected override void OnIsPausedChanged(ValueChangedEvent isPaused) + { + if (isPaused.NewValue) + this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableClock.Stop()); + else + this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); + } + + public override void Seek(double time) + { + // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. + // we may want to consider reversing the application of offsets in the future as it may feel more correct. + base.Seek(time - totalOffset); + + // manually process frame to ensure GameplayClock is correctly updated after a seek. + userOffsetClock.ProcessFrame(); + } + + public override void Restart() + { + updateRate(); + base.Restart(); + } + + /// + /// Skip forward to the next valid skip point. + /// + public void Skip() + { + if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME) + return; + + double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME; + + if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) + // double skip exception for storyboards with very long intros + skipTarget = 0; + + Seek(skipTarget); + } + + protected override GameplayClock CreateGameplayClock(IClock source) + { + // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. + // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. + platformOffsetClock = new HardwareCorrectionOffsetClock(source) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; + + // the final usable gameplay clock with user-set offsets applied. + userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); + + return localGameplayClock = new LocalGameplayClock(userOffsetClock); + } + + protected override void Update() + { + if (!IsPaused.Value) + userOffsetClock.ProcessFrame(); + + base.Update(); + } + + /// + /// Changes the backing clock to avoid using the originally provided track. + /// + public void StopUsingBeatmapClock() + { + removeSourceClockAdjustments(); + AdjustableClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); + } + + private bool speedAdjustmentsApplied; + + private void updateRate() + { + if (speedAdjustmentsApplied) + return; + + Track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + Track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + + localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); + localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); + + speedAdjustmentsApplied = true; + } + + private void removeSourceClockAdjustments() + { + if (!speedAdjustmentsApplied) + return; + + Track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + Track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + + localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); + localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); + + speedAdjustmentsApplied = false; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + removeSourceClockAdjustments(); + } + + private class HardwareCorrectionOffsetClock : FramedOffsetClock + { + // 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 * (Rate - 1); + + public HardwareCorrectionOffsetClock(IClock source, bool processSource = true) + : base(source, processSource) + { + } + } + + private class LocalGameplayClock : GameplayClock + { + public readonly List> MutableNonGameplayAdjustments = new List>(); + + public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + + public LocalGameplayClock(FramedOffsetClock underlyingClock) + : base(underlyingClock) + { + } + } + } +} From f56125bd682b68df3c3a6a20e3c24b6d758486ee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 21:15:14 +0900 Subject: [PATCH 006/400] Update clock from base class --- osu.Game/Screens/Play/GameplayClockContainer.cs | 12 +++++++++++- .../Screens/Play/MasterGameplayClockContainer.cs | 12 +++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index d8e6fda87e..6d863f0094 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -77,8 +77,18 @@ namespace osu.Game.Screens.Play Start(); } + protected override void Update() + { + if (!IsPaused.Value) + ClockToProcess.ProcessFrame(); + + base.Update(); + } + protected abstract void OnIsPausedChanged(ValueChangedEvent isPaused); - protected abstract GameplayClock CreateGameplayClock(IClock source); + protected virtual IFrameBasedClock ClockToProcess => AdjustableClock; + + protected abstract GameplayClock CreateGameplayClock(IFrameBasedClock source); } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index efc8ca732e..83e21f3c1d 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -128,7 +128,9 @@ namespace osu.Game.Screens.Play Seek(skipTarget); } - protected override GameplayClock CreateGameplayClock(IClock source) + protected override IFrameBasedClock ClockToProcess => userOffsetClock; + + protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) { // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. @@ -140,14 +142,6 @@ namespace osu.Game.Screens.Play return localGameplayClock = new LocalGameplayClock(userOffsetClock); } - protected override void Update() - { - if (!IsPaused.Value) - userOffsetClock.ProcessFrame(); - - base.Update(); - } - /// /// Changes the backing clock to avoid using the originally provided track. /// From 25b8c2f257f8a52aba5761b819ea0ee7f62b86a4 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Wed, 14 Apr 2021 00:04:03 -0400 Subject: [PATCH 007/400] Allow skipping storyboard outro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reuses SkipOverlay by calculating the endtime of the storyboard and using that as a "start point". Upon skipping the outro the score is instantly shown. When the end of the storyboard is reached the score screen automatically shows up. If the player holds ESC (pause) during the outro, the score is displayed The storyboard endtime is calculated by getting the latest endtime of the storyboard's elements, or simply returning 0 if there is no storyboard. Co-Authored-By: Marlina José --- .../Gameplay/TestSceneStoryboardWithOutro.cs | 100 ++++++++++++++++++ .../Multiplayer/MultiplayerPlayer.cs | 1 + .../Screens/Play/GameplayClockContainer.cs | 25 +++++ osu.Game/Screens/Play/Player.cs | 34 ++++++ osu.Game/Screens/Play/PlayerConfiguration.cs | 5 + osu.Game/Storyboards/Storyboard.cs | 1 + 6 files changed, 166 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs new file mode 100644 index 0000000000..1c8b33bb09 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.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.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; +using osu.Game.Storyboards; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneStoryboardWithOutro : PlayerTestScene + { + protected new OutroPlayer Player => (OutroPlayer)base.Player; + + private Storyboard storyboard; + + private const double storyboard_duration = 2000; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.SetValue(OsuSetting.ShowStoryboard, true); + storyboard = new Storyboard(); + var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + sprite.TimelineGroup.Alpha.Add(Easing.None, 0, storyboard_duration, 1, 0); + storyboard.GetLayer("Background").Add(sprite); + } + + [Test] + public void TestStoryboardSkipOutro() + { + AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); + AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); + AddAssert("score shown", () => Player.IsScoreShown); + } + + [Test] + public void TestStoryboardNoSkipOutro() + { + AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); + AddUntilStep("storyboard ends", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= storyboard_duration); + AddWaitStep("wait for score", 10); + AddAssert("score shown", () => Player.IsScoreShown); + } + + [Test] + public void TestStoryboardExitToSkipOutro() + { + AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); + AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + AddStep("exit via pause", () => Player.ExitViaPause()); + AddAssert("score shown", () => Player.IsScoreShown); + } + + protected override bool AllowFail => false; + + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OutroPlayer(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap(); + beatmap.HitObjects.Add(new HitCircle()); + return beatmap; + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => + new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio); + + protected class OutroPlayer : TestPlayer + { + public void ExitViaPause() => PerformExit(true); + + public bool IsScoreShown => !this.IsCurrentScreen() && this.GetChildScreen() is ResultsScreen; + + public OutroPlayer() + : base(false) + { + } + + protected override Task ImportScore(Score score) + { + // avoid database errors from trying to store the score + return Task.CompletedTask; + } + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index aaacf891bb..b4e8c13e83 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -49,6 +49,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer AllowPause = false, AllowRestart = false, AllowSkippingIntro = false, + AllowSkippingOutro = false, }) { this.userIds = userIds; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ddbb087962..fc45d661cf 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -228,6 +228,11 @@ namespace osu.Game.Screens.Play adjustableClock.ChangeSource(track); } + /// + /// Gets the endtime of the last element in the storyboard in ms, or start time of the last hitobject if there's no storyboard. + /// + public double StoryboardEndTime => beatmap.Storyboard.LatestEventTime ?? 0; + protected override void Update() { if (!IsPaused.Value) @@ -235,6 +240,8 @@ namespace osu.Game.Screens.Play userOffsetClock.ProcessFrame(); } + updateHasStoryboardEnded(); + base.Update(); } @@ -296,5 +303,23 @@ namespace osu.Game.Screens.Play { } } + + # region Storyboard outro logic + + public IBindable HasStoryboardEnded => hasStoryboardEnded; + + public bool HasTimeLeftInStoryboard => GameplayClock.CurrentTime <= StoryboardEndTime; + + private readonly BindableBool hasStoryboardEnded = new BindableBool(true); + + private void updateHasStoryboardEnded() + { + if (StoryboardEndTime == 0) + return; + + hasStoryboardEnded.Value = GameplayClock.CurrentTime >= StoryboardEndTime; + } + + # endregion } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index efe5d26409..13820738c7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -74,6 +74,8 @@ namespace osu.Game.Screens.Play private Bindable mouseWheelDisabled; + private Bindable storyboardEnabled; + private readonly Bindable storyboardReplacesBackground = new Bindable(); protected readonly Bindable LocalUserPlaying = new Bindable(); @@ -106,6 +108,8 @@ namespace osu.Game.Screens.Play private SkipOverlay skipOverlay; + private SkipOverlay skipOutroOverlay; + protected ScoreProcessor ScoreProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; } @@ -190,6 +194,8 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); + storyboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard); + if (game != null) gameActive.BindTo(game.IsActive); @@ -285,6 +291,9 @@ namespace osu.Game.Screens.Play ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState; HealthProcessor.Failed += onFail; + // Keep track of whether the storyboard ended after the playable portion + GameplayClockContainer.HasStoryboardEnded.ValueChanged += updateCompletionState; + foreach (var mod in Mods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); @@ -360,6 +369,10 @@ namespace osu.Game.Screens.Play { RequestSkip = performUserRequestedSkip }, + skipOutroOverlay = new SkipOverlay(GameplayClockContainer.StoryboardEndTime) + { + RequestSkip = scheduleCompletion + }, FailOverlay = new FailOverlay { OnRetry = Restart, @@ -389,6 +402,9 @@ namespace osu.Game.Screens.Play if (!Configuration.AllowSkippingIntro) skipOverlay.Expire(); + if (!Configuration.AllowSkippingOutro) + skipOutroOverlay.Expire(); + if (Configuration.AllowRestart) { container.Add(new HotkeyRetryOverlay @@ -403,6 +419,8 @@ namespace osu.Game.Screens.Play }); } + skipOutroOverlay.Hide(); + return container; } @@ -523,6 +541,14 @@ namespace osu.Game.Screens.Play Pause(); return; } + + // show the score if in storyboard outro (score has been set) + bool scoreReady = prepareScoreForDisplayTask != null && prepareScoreForDisplayTask.IsCompleted; + + if (scoreReady) + { + scheduleCompletion(); + } } this.Exit(); @@ -611,6 +637,14 @@ namespace osu.Game.Screens.Play return score.ScoreInfo; }); + // show skip overlay if storyboard is enabled and has an outro + if (storyboardEnabled.Value && GameplayClockContainer.HasTimeLeftInStoryboard) + { + skipOutroOverlay.Show(); + completionProgressDelegate = null; + return; + } + using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY)) scheduleCompletion(); } diff --git a/osu.Game/Screens/Play/PlayerConfiguration.cs b/osu.Game/Screens/Play/PlayerConfiguration.cs index cd30ead638..ad29563d54 100644 --- a/osu.Game/Screens/Play/PlayerConfiguration.cs +++ b/osu.Game/Screens/Play/PlayerConfiguration.cs @@ -24,5 +24,10 @@ namespace osu.Game.Screens.Play /// Whether the player should be allowed to skip the intro, advancing to the start of gameplay. /// public bool AllowSkippingIntro { get; set; } = true; + + /// + /// Whether the player should be allowed to skip the outro, advancing to the end of a storyboard. + /// + public bool AllowSkippingOutro { get; set; } = true; } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 41058d9cb3..e22c586048 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -45,6 +45,7 @@ namespace osu.Game.Storyboards /// Videos and samples return StartTime as their EndTIme. /// public double? LatestEventTime => Layers.SelectMany(l => l.Elements).OrderByDescending(e => e.EndTime).FirstOrDefault()?.EndTime; + /// /// Depth of the currently front-most storyboard layer, excluding the overlay layer. /// From 0e545e1ed9df1d33e0d6506a9edc3a053d6aeb7a Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Thu, 15 Apr 2021 17:17:02 -0400 Subject: [PATCH 008/400] Add IHasDuration interface with EndTime for storyboard elements to implement --- osu.Game/Storyboards/IHasDuration.cs | 10 ++++++++++ osu.Game/Storyboards/IStoryboardElement.cs | 2 -- osu.Game/Storyboards/Storyboard.cs | 6 +----- osu.Game/Storyboards/StoryboardSample.cs | 2 -- osu.Game/Storyboards/StoryboardSprite.cs | 2 +- osu.Game/Storyboards/StoryboardVideo.cs | 2 -- 6 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 osu.Game/Storyboards/IHasDuration.cs diff --git a/osu.Game/Storyboards/IHasDuration.cs b/osu.Game/Storyboards/IHasDuration.cs new file mode 100644 index 0000000000..98f75b8ee2 --- /dev/null +++ b/osu.Game/Storyboards/IHasDuration.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.Storyboards +{ + public interface IHasDuration + { + double EndTime { get; } + } +} diff --git a/osu.Game/Storyboards/IStoryboardElement.cs b/osu.Game/Storyboards/IStoryboardElement.cs index 03f8b97212..c4c150a8a4 100644 --- a/osu.Game/Storyboards/IStoryboardElement.cs +++ b/osu.Game/Storyboards/IStoryboardElement.cs @@ -12,8 +12,6 @@ namespace osu.Game.Storyboards double StartTime { get; } - double EndTime { get; } - Drawable CreateDrawable(); } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index e22c586048..669f6ccc43 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -40,11 +40,7 @@ namespace osu.Game.Storyboards /// Across all layers, find the latest point in time that a storyboard element ends at. /// Will return null if there are no elements. /// - /// - /// This iterates all elements and as such should be used sparingly or stored locally. - /// Videos and samples return StartTime as their EndTIme. - /// - public double? LatestEventTime => Layers.SelectMany(l => l.Elements).OrderByDescending(e => e.EndTime).FirstOrDefault()?.EndTime; + public double? LatestEventTime => Layers.SelectMany(l => l.Elements.OfType()).OrderByDescending(e => e.EndTime).FirstOrDefault()?.EndTime; /// /// Depth of the currently front-most storyboard layer, excluding the overlay layer. diff --git a/osu.Game/Storyboards/StoryboardSample.cs b/osu.Game/Storyboards/StoryboardSample.cs index d0949c93a7..5d6ce215f5 100644 --- a/osu.Game/Storyboards/StoryboardSample.cs +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -15,8 +15,6 @@ namespace osu.Game.Storyboards public double StartTime { get; } - public double EndTime => StartTime; - public int Volume { get; } public IEnumerable LookupNames => new[] diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index fdaa59d7d9..8f2c1e9e0c 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -11,7 +11,7 @@ using JetBrains.Annotations; namespace osu.Game.Storyboards { - public class StoryboardSprite : IStoryboardElement + public class StoryboardSprite : IStoryboardElement, IHasDuration { private readonly List loops = new List(); private readonly List triggers = new List(); diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs index 1314bd7cb9..4652e45852 100644 --- a/osu.Game/Storyboards/StoryboardVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -14,8 +14,6 @@ namespace osu.Game.Storyboards public double StartTime { get; } - public double EndTime => StartTime; - public StoryboardVideo(string path, int offset) { Path = path; From b15838b22027746fd9174e5fb72e10ade6a68cd2 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Fri, 16 Apr 2021 00:59:10 -0400 Subject: [PATCH 009/400] Move storyboard outro logic to DrawableStoryboard --- .../Gameplay/TestSceneStoryboardWithOutro.cs | 17 +++++++++++-- .../Multiplayer/MultiplayerPlayer.cs | 3 +-- osu.Game/Screens/Play/DimmableStoryboard.cs | 3 +++ .../Screens/Play/GameplayClockContainer.cs | 20 --------------- osu.Game/Screens/Play/Player.cs | 25 ++++++++----------- osu.Game/Screens/Play/PlayerConfiguration.cs | 9 ++----- .../Drawables/DrawableStoryboard.cs | 23 +++++++++++++++++ ...on.cs => IStoryboardElementHasDuration.cs} | 2 +- osu.Game/Storyboards/Storyboard.cs | 2 +- osu.Game/Storyboards/StoryboardSprite.cs | 2 +- 10 files changed, 57 insertions(+), 49 deletions(-) rename osu.Game/Storyboards/{IHasDuration.cs => IStoryboardElementHasDuration.cs} (81%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 1c8b33bb09..27d203d5d6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -6,12 +6,14 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Storyboards; using osuTK; @@ -36,6 +38,16 @@ namespace osu.Game.Tests.Visual.Gameplay storyboard.GetLayer("Background").Add(sprite); } + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("ignore user settings", () => + { + Player.DimmableStoryboard.IgnoreUserSettings.Value = true; + }); + } + [Test] public void TestStoryboardSkipOutro() { @@ -50,8 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); AddUntilStep("storyboard ends", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= storyboard_duration); - AddWaitStep("wait for score", 10); - AddAssert("score shown", () => Player.IsScoreShown); + AddUntilStep("wait for score shown", () => Player.IsScoreShown); } [Test] @@ -85,6 +96,8 @@ namespace osu.Game.Tests.Visual.Gameplay public bool IsScoreShown => !this.IsCurrentScreen() && this.GetChildScreen() is ResultsScreen; + public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; + public OutroPlayer() : base(false) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index b4e8c13e83..ae2042fbe8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -48,8 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { AllowPause = false, AllowRestart = false, - AllowSkippingIntro = false, - AllowSkippingOutro = false, + AllowSkipping = false, }) { this.userIds = userIds; diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 58eb95b7c6..bebde1b15b 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.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.Containers; using osu.Game.Graphics.Containers; using osu.Game.Storyboards; @@ -61,5 +62,7 @@ namespace osu.Game.Screens.Play Add(storyboard); OverlayLayerContainer.Add(storyboard.OverlayLayer.CreateProxy()); } + + public IBindable HasStoryboardEnded => drawableStoryboard?.HasStoryboardEnded; } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index fc45d661cf..95419c0b35 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -240,8 +240,6 @@ namespace osu.Game.Screens.Play userOffsetClock.ProcessFrame(); } - updateHasStoryboardEnded(); - base.Update(); } @@ -303,23 +301,5 @@ namespace osu.Game.Screens.Play { } } - - # region Storyboard outro logic - - public IBindable HasStoryboardEnded => hasStoryboardEnded; - - public bool HasTimeLeftInStoryboard => GameplayClock.CurrentTime <= StoryboardEndTime; - - private readonly BindableBool hasStoryboardEnded = new BindableBool(true); - - private void updateHasStoryboardEnded() - { - if (StoryboardEndTime == 0) - return; - - hasStoryboardEnded.Value = GameplayClock.CurrentTime >= StoryboardEndTime; - } - - # endregion } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 14ba9e7e02..81d2621647 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -74,8 +74,6 @@ namespace osu.Game.Screens.Play private Bindable mouseWheelDisabled; - private Bindable storyboardEnabled; - private readonly Bindable storyboardReplacesBackground = new Bindable(); protected readonly Bindable LocalUserPlaying = new Bindable(); @@ -106,7 +104,7 @@ namespace osu.Game.Screens.Play private BreakTracker breakTracker; - private SkipOverlay skipOverlay; + private SkipOverlay skipIntroOverlay; private SkipOverlay skipOutroOverlay; @@ -194,8 +192,6 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - storyboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard); - if (game != null) gameActive.BindTo(game.IsActive); @@ -250,7 +246,7 @@ namespace osu.Game.Screens.Play HUDOverlay.ShowHud.Value = false; HUDOverlay.ShowHud.Disabled = true; BreakOverlay.Hide(); - skipOverlay.Hide(); + skipIntroOverlay.Hide(); } DrawableRuleset.FrameStableClock.WaitingOnFrames.BindValueChanged(waiting => @@ -292,7 +288,8 @@ namespace osu.Game.Screens.Play HealthProcessor.Failed += onFail; // Keep track of whether the storyboard ended after the playable portion - GameplayClockContainer.HasStoryboardEnded.ValueChanged += updateCompletionState; + if (DimmableStoryboard.HasStoryboardEnded != null) + DimmableStoryboard.HasStoryboardEnded.ValueChanged += updateCompletionState; foreach (var mod in Mods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); @@ -365,7 +362,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, - skipOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime) + skipIntroOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime) { RequestSkip = performUserRequestedSkip }, @@ -399,11 +396,8 @@ namespace osu.Game.Screens.Play } }; - if (!Configuration.AllowSkippingIntro) - skipOverlay.Expire(); - - if (!Configuration.AllowSkippingOutro) - skipOutroOverlay.Expire(); + if (!Configuration.AllowSkipping) + skipIntroOverlay.Expire(); if (Configuration.AllowRestart) { @@ -637,8 +631,9 @@ namespace osu.Game.Screens.Play return score.ScoreInfo; }); - // show skip overlay if storyboard is enabled and has an outro - if (storyboardEnabled.Value && GameplayClockContainer.HasTimeLeftInStoryboard) + var storyboardHasOutro = DimmableStoryboard.ContentDisplayed && (!DimmableStoryboard.HasStoryboardEnded?.Value ?? false); + + if (storyboardHasOutro) { skipOutroOverlay.Show(); completionProgressDelegate = null; diff --git a/osu.Game/Screens/Play/PlayerConfiguration.cs b/osu.Game/Screens/Play/PlayerConfiguration.cs index ad29563d54..18ee73374f 100644 --- a/osu.Game/Screens/Play/PlayerConfiguration.cs +++ b/osu.Game/Screens/Play/PlayerConfiguration.cs @@ -21,13 +21,8 @@ namespace osu.Game.Screens.Play public bool AllowRestart { get; set; } = true; /// - /// Whether the player should be allowed to skip the intro, advancing to the start of gameplay. + /// Whether the player should be allowed to skip intros/outros, advancing to the start of gameplay or the end of a storyboard. /// - public bool AllowSkippingIntro { get; set; } = true; - - /// - /// Whether the player should be allowed to skip the outro, advancing to the end of a storyboard. - /// - public bool AllowSkippingOutro { get; set; } = true; + public bool AllowSkipping { get; set; } = true; } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index af4615c895..08c2bf1b4a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using osuTK; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; @@ -82,5 +83,27 @@ namespace osu.Game.Storyboards.Drawables foreach (var layer in Children) layer.Enabled = passing ? layer.Layer.VisibleWhenPassing : layer.Layer.VisibleWhenFailing; } + + protected override void Update() + { + base.Update(); + updateHasStoryboardEnded(); + } + + /// + /// Whether the storyboard has ended after the gameplay portion of the beatmap. + /// + public IBindable HasStoryboardEnded => hasStoryboardEnded; + + private readonly BindableBool hasStoryboardEnded = new BindableBool(true); + + private void updateHasStoryboardEnded() + { + if (Storyboard.LatestEventTime == null) + return; + + var time = Clock.CurrentTime; + hasStoryboardEnded.Value = time >= Storyboard.LatestEventTime; + } } } diff --git a/osu.Game/Storyboards/IHasDuration.cs b/osu.Game/Storyboards/IStoryboardElementHasDuration.cs similarity index 81% rename from osu.Game/Storyboards/IHasDuration.cs rename to osu.Game/Storyboards/IStoryboardElementHasDuration.cs index 98f75b8ee2..e6ee373812 100644 --- a/osu.Game/Storyboards/IHasDuration.cs +++ b/osu.Game/Storyboards/IStoryboardElementHasDuration.cs @@ -3,7 +3,7 @@ namespace osu.Game.Storyboards { - public interface IHasDuration + public interface IStoryboardElementHasDuration { double EndTime { get; } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 669f6ccc43..4fa64b7c34 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -40,7 +40,7 @@ namespace osu.Game.Storyboards /// Across all layers, find the latest point in time that a storyboard element ends at. /// Will return null if there are no elements. /// - public double? LatestEventTime => Layers.SelectMany(l => l.Elements.OfType()).OrderByDescending(e => e.EndTime).FirstOrDefault()?.EndTime; + public double? LatestEventTime => Layers.SelectMany(l => l.Elements.OfType()).OrderByDescending(e => e.EndTime).FirstOrDefault()?.EndTime; /// /// Depth of the currently front-most storyboard layer, excluding the overlay layer. diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 8f2c1e9e0c..51bdce3321 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -11,7 +11,7 @@ using JetBrains.Annotations; namespace osu.Game.Storyboards { - public class StoryboardSprite : IStoryboardElement, IHasDuration + public class StoryboardSprite : IStoryboardElement, IStoryboardElementHasDuration { private readonly List loops = new List(); private readonly List triggers = new List(); From 33a665224e6c1f77846e9f03927fa72b4cd3859f Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Fri, 16 Apr 2021 01:03:15 -0400 Subject: [PATCH 010/400] Clean up skipOutroOverlay if skipping is disabled --- osu.Game/Screens/Play/Player.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 81d2621647..7ad9c1a8af 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -397,7 +397,10 @@ namespace osu.Game.Screens.Play }; if (!Configuration.AllowSkipping) + { skipIntroOverlay.Expire(); + skipOutroOverlay.Expire(); + } if (Configuration.AllowRestart) { From 5652490d61c4ee2c5b046e8baf7e5524303a1a31 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 14:11:55 +0900 Subject: [PATCH 011/400] Fix OnUserBeganPlaying not being invoked if already watching --- .../Visual/Gameplay/TestSceneSpectator.cs | 24 ++++++++++++++++ .../Spectator/SpectatorStreamingClient.cs | 28 +++++++++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 397b37718d..ea66144b21 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.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 System.Threading; @@ -204,6 +205,29 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator); } + [Test] + public void OnUserBeganPlayingCallbackInvokedOnNewAdd() + { + bool callbackInvoked = false; + Action callbackAction = (_, __) => callbackInvoked = true; + + AddStep("bind first event", () => testSpectatorStreamingClient.OnUserBeganPlaying += callbackAction); + start(); + AddAssert("callback invoked", () => callbackInvoked); + + AddStep("reset", () => + { + testSpectatorStreamingClient.OnUserBeganPlaying -= callbackAction; + callbackInvoked = false; + }); + + AddStep("bind event again", () => testSpectatorStreamingClient.OnUserBeganPlaying += callbackAction); + AddAssert("callback invoked", () => callbackInvoked); + + // Don't leave the event bound if test run succeeded. + AddStep("reset", () => testSpectatorStreamingClient.OnUserBeganPlaying -= callbackAction); + } + private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 3a586874fe..7bea49e102 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -60,6 +60,7 @@ namespace osu.Game.Online.Spectator private IBindable> currentMods { get; set; } private readonly SpectatorState currentState = new SpectatorState(); + private readonly Dictionary currentUserStates = new Dictionary(); private bool isPlaying; @@ -68,10 +69,25 @@ namespace osu.Game.Online.Spectator /// public event Action OnNewFrames; + private event Action onUserBeganPlaying; + /// - /// Called whenever a user starts a play session. + /// Called whenever a user starts a play session, or immediately if the user is being watched and currently in a play session. /// - public event Action OnUserBeganPlaying; + public event Action OnUserBeganPlaying + { + add + { + onUserBeganPlaying += value; + + lock (userLock) + { + foreach (var (userId, state) in currentUserStates) + value?.Invoke(userId, state); + } + } + remove => onUserBeganPlaying -= value; + } /// /// Called whenever a user finishes a play session. @@ -134,7 +150,10 @@ namespace osu.Game.Online.Spectator if (!playingUsers.Contains(userId)) playingUsers.Add(userId); - OnUserBeganPlaying?.Invoke(userId, state); + lock (userLock) + currentUserStates[userId] = state; + + onUserBeganPlaying?.Invoke(userId, state); return Task.CompletedTask; } @@ -143,6 +162,9 @@ namespace osu.Game.Online.Spectator { playingUsers.Remove(userId); + lock (userLock) + currentUserStates.Remove(userId); + OnUserFinishedPlaying?.Invoke(userId, state); return Task.CompletedTask; From ca74f413cd10ccaee2dd1e51c5190019262b76c8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 17:29:42 +0900 Subject: [PATCH 012/400] Change to explicit method instead --- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Spectator/SpectatorStreamingClient.cs | 38 ++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index ea66144b21..def662d3ea 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -221,7 +221,7 @@ namespace osu.Game.Tests.Visual.Gameplay callbackInvoked = false; }); - AddStep("bind event again", () => testSpectatorStreamingClient.OnUserBeganPlaying += callbackAction); + AddStep("bind event with run once immediately", () => testSpectatorStreamingClient.BindUserBeganPlaying(callbackAction, true)); AddAssert("callback invoked", () => callbackInvoked); // Don't leave the event bound if test run succeeded. diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 7bea49e102..4bbc420223 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -69,25 +69,10 @@ namespace osu.Game.Online.Spectator /// public event Action OnNewFrames; - private event Action onUserBeganPlaying; - /// /// Called whenever a user starts a play session, or immediately if the user is being watched and currently in a play session. /// - public event Action OnUserBeganPlaying - { - add - { - onUserBeganPlaying += value; - - lock (userLock) - { - foreach (var (userId, state) in currentUserStates) - value?.Invoke(userId, state); - } - } - remove => onUserBeganPlaying -= value; - } + public event Action OnUserBeganPlaying; /// /// Called whenever a user finishes a play session. @@ -153,7 +138,7 @@ namespace osu.Game.Online.Spectator lock (userLock) currentUserStates[userId] = state; - onUserBeganPlaying?.Invoke(userId, state); + OnUserBeganPlaying?.Invoke(userId, state); return Task.CompletedTask; } @@ -290,5 +275,24 @@ namespace osu.Game.Online.Spectator lastSendTime = Time.Current; } + + /// + /// Bind an action to with the option of running the bound action once immediately. + /// + /// The action to perform when a user begins playing. + /// Whether the action provided in should be run once immediately for all users currently playing. + public void BindUserBeganPlaying(Action callback, bool runOnceImmediately = false) + { + OnUserBeganPlaying += callback; + + if (!runOnceImmediately) + return; + + lock (userLock) + { + foreach (var (userId, state) in currentUserStates) + callback(userId, state); + } + } } } From 377e5ce6b396c585a0652cf63253966c59c06948 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 18:21:35 +0900 Subject: [PATCH 013/400] Fix test incorrect sending state too often --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index def662d3ea..392419649b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -312,7 +312,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override void WatchUser(int userId) { - if (sentState) + if (!PlayingUsers.Contains(userId) && sentState) { // usually the server would do this. sendState(beatmapId); From 46d2181d42930ac61f6da22f8146ba2b74357929 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 18:21:56 +0900 Subject: [PATCH 014/400] Remove now unnecessary (duplicating) test --- .../Visual/Gameplay/TestSceneSpectator.cs | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 392419649b..74ce66096e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -205,29 +204,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator); } - [Test] - public void OnUserBeganPlayingCallbackInvokedOnNewAdd() - { - bool callbackInvoked = false; - Action callbackAction = (_, __) => callbackInvoked = true; - - AddStep("bind first event", () => testSpectatorStreamingClient.OnUserBeganPlaying += callbackAction); - start(); - AddAssert("callback invoked", () => callbackInvoked); - - AddStep("reset", () => - { - testSpectatorStreamingClient.OnUserBeganPlaying -= callbackAction; - callbackInvoked = false; - }); - - AddStep("bind event with run once immediately", () => testSpectatorStreamingClient.BindUserBeganPlaying(callbackAction, true)); - AddAssert("callback invoked", () => callbackInvoked); - - // Don't leave the event bound if test run succeeded. - AddStep("reset", () => testSpectatorStreamingClient.OnUserBeganPlaying -= callbackAction); - } - private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; From 274e33184b8afb9d10ef8b2d380edc8b742ee2ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 18:22:22 +0900 Subject: [PATCH 015/400] Fix SpectatorScreen potentially missing user playing callbacks --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 42 ++++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 6dd3144fc8..7be6c6183b 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -62,26 +62,42 @@ namespace osu.Game.Screens.Spectate { base.LoadComplete(); - spectatorClient.OnUserBeganPlaying += userBeganPlaying; - spectatorClient.OnUserFinishedPlaying += userFinishedPlaying; - spectatorClient.OnNewFrames += userSentFrames; - - foreach (var id in userIds) + populateAllUsers().ContinueWith(_ => Schedule(() => { - userLookupCache.GetUserAsync(id).ContinueWith(u => Schedule(() => + spectatorClient.BindUserBeganPlaying(userBeganPlaying, true); + spectatorClient.OnUserFinishedPlaying += userFinishedPlaying; + spectatorClient.OnNewFrames += userSentFrames; + + managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(beatmapUpdated); + + lock (stateLock) { - if (u.Result == null) + foreach (var (id, _) in userMap) + spectatorClient.WatchUser(id); + } + })); + } + + private Task populateAllUsers() + { + var userLookupTasks = new Task[userIds.Length]; + + for (int i = 0; i < userIds.Length; i++) + { + var userId = userIds[i]; + + userLookupTasks[i] = userLookupCache.GetUserAsync(userId).ContinueWith(task => + { + if (!task.IsCompletedSuccessfully) return; lock (stateLock) - userMap[id] = u.Result; - - spectatorClient.WatchUser(id); - }), TaskContinuationOptions.OnlyOnRanToCompletion); + userMap[userId] = task.Result; + }); } - managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(beatmapUpdated); + return Task.WhenAll(userLookupTasks); } private void beatmapUpdated(ValueChangedEvent> e) From 6301111fa3c1d274c68766d3c1456ebd74d95138 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:15:42 +0900 Subject: [PATCH 016/400] Remove ClockToProcess, always process underlying clock --- osu.Game/Screens/Play/GameplayClock.cs | 18 +++++++++--------- .../Screens/Play/GameplayClockContainer.cs | 4 +--- .../Play/MasterGameplayClockContainer.cs | 2 -- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index db4b5d300b..54aa395f5f 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play /// public class GameplayClock : IFrameBasedClock { - private readonly IFrameBasedClock underlyingClock; + internal readonly IFrameBasedClock UnderlyingClock; public readonly BindableBool IsPaused = new BindableBool(); @@ -30,12 +30,12 @@ namespace osu.Game.Screens.Play public GameplayClock(IFrameBasedClock underlyingClock) { - this.underlyingClock = underlyingClock; + UnderlyingClock = underlyingClock; } - public double CurrentTime => underlyingClock.CurrentTime; + public double CurrentTime => UnderlyingClock.CurrentTime; - public double Rate => underlyingClock.Rate; + public double Rate => UnderlyingClock.Rate; /// /// The rate of gameplay when playback is at 100%. @@ -59,19 +59,19 @@ namespace osu.Game.Screens.Play } } - public bool IsRunning => underlyingClock.IsRunning; + public bool IsRunning => UnderlyingClock.IsRunning; public void ProcessFrame() { // intentionally not updating the underlying clock (handled externally). } - public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; + public double ElapsedFrameTime => UnderlyingClock.ElapsedFrameTime; - public double FramesPerSecond => underlyingClock.FramesPerSecond; + public double FramesPerSecond => UnderlyingClock.FramesPerSecond; - public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; + public FrameTimeInfo TimeInfo => UnderlyingClock.TimeInfo; - public IClock Source => underlyingClock; + public IClock Source => UnderlyingClock; } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6d863f0094..b7dc55277f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -80,15 +80,13 @@ namespace osu.Game.Screens.Play protected override void Update() { if (!IsPaused.Value) - ClockToProcess.ProcessFrame(); + GameplayClock.UnderlyingClock.ProcessFrame(); base.Update(); } protected abstract void OnIsPausedChanged(ValueChangedEvent isPaused); - protected virtual IFrameBasedClock ClockToProcess => AdjustableClock; - protected abstract GameplayClock CreateGameplayClock(IFrameBasedClock source); } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 83e21f3c1d..5eb82bf0fa 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -128,8 +128,6 @@ namespace osu.Game.Screens.Play Seek(skipTarget); } - protected override IFrameBasedClock ClockToProcess => userOffsetClock; - protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) { // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. From 3a78c19f9695ad5519e8494e8e888f25d50998b9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:33:29 +0900 Subject: [PATCH 017/400] More refactoring/xmldocs --- .../Screens/Play/GameplayClockContainer.cs | 64 ++++++++++++++----- .../Play/MasterGameplayClockContainer.cs | 16 +++-- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index b7dc55277f..642ede5f1c 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -9,26 +9,36 @@ using osu.Framework.Timing; namespace osu.Game.Screens.Play { + /// + /// Encapsulates gameplay timing logic and provides a via DI for gameplay components to use. + /// public abstract class GameplayClockContainer : Container { /// - /// The final clock which is exposed to underlying components. + /// The final clock which is exposed to gameplay components. /// public GameplayClock GameplayClock { get; private set; } + /// + /// Whether gameplay is paused. + /// public readonly BindableBool IsPaused = new BindableBool(); /// - /// The decoupled clock used for gameplay. Should be used for seeks and clock control. + /// The adjustable source clock used for gameplay. Should be used for seeks and clock control. /// - protected readonly DecoupleableInterpolatingFramedClock AdjustableClock; + protected readonly DecoupleableInterpolatingFramedClock AdjustableSource; + /// + /// Creates a new . + /// + /// The source used for timing. protected GameplayClockContainer(IClock sourceClock) { RelativeSizeAxes = Axes.Both; - AdjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - AdjustableClock.ChangeSource(sourceClock); + AdjustableSource = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + AdjustableSource.ChangeSource(sourceClock); IsPaused.BindValueChanged(OnIsPausedChanged); } @@ -37,21 +47,24 @@ namespace osu.Game.Screens.Play { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(GameplayClock = CreateGameplayClock(AdjustableClock)); + dependencies.CacheAs(GameplayClock = CreateGameplayClock(AdjustableSource)); GameplayClock.IsPaused.BindTo(IsPaused); return dependencies; } + /// + /// Starts gameplay. + /// public virtual void Start() { - if (!AdjustableClock.IsRunning) + if (!AdjustableSource.IsRunning) { // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time // This accounts for the clock source potentially taking time to enter a completely stopped state Seek(GameplayClock.CurrentTime); - AdjustableClock.Start(); + AdjustableSource.Start(); } IsPaused.Value = false; @@ -59,19 +72,22 @@ namespace osu.Game.Screens.Play /// /// Seek to a specific time in gameplay. - /// - /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). - /// /// /// The destination time to seek to. - public virtual void Seek(double time) => AdjustableClock.Seek(time); + public virtual void Seek(double time) => AdjustableSource.Seek(time); + /// + /// Stops gameplay. + /// public virtual void Stop() => IsPaused.Value = true; + /// + /// Restarts gameplay. + /// public virtual void Restart() { - AdjustableClock.Seek(0); - AdjustableClock.Stop(); + AdjustableSource.Seek(0); + AdjustableSource.Stop(); if (!IsPaused.Value) Start(); @@ -85,8 +101,26 @@ namespace osu.Game.Screens.Play base.Update(); } - protected abstract void OnIsPausedChanged(ValueChangedEvent isPaused); + /// + /// Invoked when the value of is changed to start or stop the clock. + /// + /// Whether the clock should now be paused. + protected virtual void OnIsPausedChanged(ValueChangedEvent isPaused) + { + if (isPaused.NewValue) + AdjustableSource.Stop(); + else + AdjustableSource.Start(); + } + /// + /// Creates the final which is exposed via DI to be used by gameplay components. + /// + /// + /// Any intermediate clocks such as platform offsets should be applied here. + /// + /// The providing the source time. + /// The final . protected abstract GameplayClock CreateGameplayClock(IFrameBasedClock source); } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 5eb82bf0fa..e7b4645734 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play /// public const double MINIMUM_SKIP_TIME = 1000; - protected Track Track => (Track)AdjustableClock.Source; + protected Track Track => (Track)AdjustableSource.Source; public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { @@ -84,17 +84,25 @@ namespace osu.Game.Screens.Play Seek(startTime); - AdjustableClock.ProcessFrame(); + AdjustableSource.ProcessFrame(); } protected override void OnIsPausedChanged(ValueChangedEvent isPaused) { + // The source is stopped by a frequency fade first. if (isPaused.NewValue) - this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableClock.Stop()); + this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableSource.Stop()); else this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } + /// + /// Seek to a specific time in gameplay. + /// + /// + /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). + /// + /// The destination time to seek to. public override void Seek(double time) { // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. @@ -146,7 +154,7 @@ namespace osu.Game.Screens.Play public void StopUsingBeatmapClock() { removeSourceClockAdjustments(); - AdjustableClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); + AdjustableSource.ChangeSource(new TrackVirtual(beatmap.Track.Length)); } private bool speedAdjustmentsApplied; From 314b1646bd22db171ecf35c805860dd578394b41 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:47:09 +0900 Subject: [PATCH 018/400] Add xmldoc to MasterGameplayClockContainer --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index e7b4645734..db0aa23001 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -16,6 +16,16 @@ using osu.Game.Configuration; namespace osu.Game.Screens.Play { + /// + /// A which uses a as a source. + /// + /// This is the most complete which takes into account all user and platform offsets, + /// and provides implementations for user actions such as skipping or adjusting playback rates that may occur during gameplay. + /// + /// + /// + /// This is intended to be used as a single controller for gameplay, or as a reference source for other s. + /// public class MasterGameplayClockContainer : GameplayClockContainer { /// From 44e13a91ade2e0aa18fb8d159eebed51e9b30787 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:51:42 +0900 Subject: [PATCH 019/400] Rename test scene to match class --- ...ockContainer.cs => TestSceneMasterGameplayClockContainer.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Gameplay/{TestSceneGameplayClockContainer.cs => TestSceneMasterGameplayClockContainer.cs} (92%) diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs similarity index 92% rename from osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs rename to osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 4d5dcabbba..77ada958d7 100644 --- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -10,7 +10,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Gameplay { [HeadlessTest] - public class TestSceneGameplayClockContainer : OsuTestScene + public class TestSceneMasterGameplayClockContainer : OsuTestScene { [Test] public void TestStartThenElapsedTime() From cf3aaff7bd1bf796f826d18f8a56c7a07d1359c6 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 17 Apr 2021 16:01:23 +0200 Subject: [PATCH 020/400] Add floating fruits mod --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 3 ++- .../Mods/CatchModFloatingFruits.cs | 23 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index f4ddbd3021..6206815728 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -126,7 +126,8 @@ namespace osu.Game.Rulesets.Catch case ModType.Fun: return new Mod[] { - new MultiMod(new ModWindUp(), new ModWindDown()) + new MultiMod(new ModWindUp(), new ModWindDown()), + new CatchModFloatingFruits() }; default: diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs new file mode 100644 index 0000000000..4e25739521 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -0,0 +1,23 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModFloatingFruits : Mod, IApplicableToPlayer + { + public override string Name => "Floating Fruits"; + public override string Acronym => "FF"; + public override string Description => "The fruits are... floating?"; + public override double ScoreMultiplier => 1; + public override IconUsage? Icon => FontAwesome.Brands.Fly; + + public void ApplyToPlayer(Player player) + { + player.DrawableRuleset.Anchor = Anchor.Centre; + player.DrawableRuleset.Origin = Anchor.Centre; + player.DrawableRuleset.Scale = new osuTK.Vector2(1, -1); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dd3f58439b..59231a1c30 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Play protected HealthProcessor HealthProcessor { get; private set; } - protected DrawableRuleset DrawableRuleset { get; private set; } + public DrawableRuleset DrawableRuleset { get; set; } protected HUDOverlay HUDOverlay { get; private set; } From 5d274dbce86b3b8b03abb72f176e57a4209c4576 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 17 Apr 2021 16:38:28 +0200 Subject: [PATCH 021/400] replace IApplicableToPlayer with IApplicableToDrawableRuleset --- .../Mods/CatchModFloatingFruits.cs | 13 ++++++++----- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 4e25739521..4e34cbe9e2 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -1,11 +1,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModFloatingFruits : Mod, IApplicableToPlayer + public class CatchModFloatingFruits : Mod, IApplicableToDrawableRuleset { public override string Name => "Floating Fruits"; public override string Acronym => "FF"; @@ -13,11 +15,12 @@ namespace osu.Game.Rulesets.Catch.Mods public override double ScoreMultiplier => 1; public override IconUsage? Icon => FontAwesome.Brands.Fly; - public void ApplyToPlayer(Player player) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - player.DrawableRuleset.Anchor = Anchor.Centre; - player.DrawableRuleset.Origin = Anchor.Centre; - player.DrawableRuleset.Scale = new osuTK.Vector2(1, -1); + drawableRuleset.Anchor = Anchor.Centre; + drawableRuleset.Origin = Anchor.Centre; + drawableRuleset.Scale = new osuTK.Vector2(1, -1); } + } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 59231a1c30..dd3f58439b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Play protected HealthProcessor HealthProcessor { get; private set; } - public DrawableRuleset DrawableRuleset { get; set; } + protected DrawableRuleset DrawableRuleset { get; private set; } protected HUDOverlay HUDOverlay { get; private set; } From 448574e7e67cba8ead7bf1c27e0b892d9fec3754 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 17 Apr 2021 17:33:53 +0200 Subject: [PATCH 022/400] Use `WorkingBeatmap` instead of `IBeatmap` This lets us access things like the background, track, etc. which are necessary for quality and filesize checks. Also improves the structure of the `CheckBackgroundTest` class in the process. --- .../Checks/CheckOffscreenObjectsTest.cs | 70 ++++++++----------- .../Edit/Checks/CheckOffscreenObjects.cs | 4 +- .../Edit/OsuBeatmapVerifier.cs | 2 +- .../Editing/Checks/CheckBackgroundTest.cs | 7 +- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 2 +- .../Rulesets/Edit/Checks/CheckBackground.cs | 10 +-- .../Rulesets/Edit/Checks/Components/ICheck.cs | 4 +- osu.Game/Rulesets/Edit/IBeatmapVerifier.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 3 +- 9 files changed, 46 insertions(+), 58 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index f9445a9a96..db347960ef 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Checks; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Tests.Beatmaps; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks @@ -30,25 +31,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks [Test] public void TestCircleInCenter() { - var beatmap = new Beatmap + assertOk(new Beatmap { HitObjects = new List { new HitCircle { StartTime = 3000, - Position = playfield_centre // Playfield is 640 x 480. + Position = playfield_centre } } - }; - - Assert.That(check.Run(beatmap), Is.Empty); + }); } [Test] public void TestCircleNearEdge() { - var beatmap = new Beatmap + assertOk(new Beatmap { HitObjects = new List { @@ -58,15 +57,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = new Vector2(5, 5) } } - }; - - Assert.That(check.Run(beatmap), Is.Empty); + }); } [Test] public void TestCircleNearEdgeStackedOffscreen() { - var beatmap = new Beatmap + assertOffscreenCircle(new Beatmap { HitObjects = new List { @@ -77,15 +74,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks StackHeight = 5 } } - }; - - assertOffscreenCircle(beatmap); + }); } [Test] public void TestCircleOffscreen() { - var beatmap = new Beatmap + assertOffscreenCircle(new Beatmap { HitObjects = new List { @@ -95,15 +90,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = new Vector2(0, 0) } } - }; - - assertOffscreenCircle(beatmap); + }); } [Test] public void TestSliderInCenter() { - var beatmap = new Beatmap + assertOk(new Beatmap { HitObjects = new List { @@ -118,15 +111,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks }), } } - }; - - Assert.That(check.Run(beatmap), Is.Empty); + }); } [Test] public void TestSliderNearEdge() { - var beatmap = new Beatmap + assertOk(new Beatmap { HitObjects = new List { @@ -141,15 +132,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks }), } } - }; - - Assert.That(check.Run(beatmap), Is.Empty); + }); } [Test] public void TestSliderNearEdgeStackedOffscreen() { - var beatmap = new Beatmap + assertOk(new Beatmap { HitObjects = new List { @@ -165,15 +154,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks StackHeight = 5 } } - }; - - assertOffscreenSlider(beatmap); + }); } [Test] public void TestSliderOffscreenStart() { - var beatmap = new Beatmap + assertOffscreenSlider(new Beatmap { HitObjects = new List { @@ -188,15 +175,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks }), } } - }; - - assertOffscreenSlider(beatmap); + }); } [Test] public void TestSliderOffscreenEnd() { - var beatmap = new Beatmap + assertOffscreenSlider(new Beatmap { HitObjects = new List { @@ -211,15 +196,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks }), } } - }; - - assertOffscreenSlider(beatmap); + }); } [Test] public void TestSliderOffscreenPath() { - var beatmap = new Beatmap + assertOffscreenSlider(new Beatmap { HitObjects = new List { @@ -236,14 +219,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks }), } } - }; + }); + } - assertOffscreenSlider(beatmap); + private void assertOk(IBeatmap beatmap) + { + Assert.That(check.Run(new TestWorkingBeatmap(beatmap)), Is.Empty); } private void assertOffscreenCircle(IBeatmap beatmap) { - var issues = check.Run(beatmap).ToList(); + var issues = check.Run(new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle); @@ -251,7 +237,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOffscreenSlider(IBeatmap beatmap) { - var issues = check.Run(beatmap).ToList(); + var issues = check.Run(new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider); diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index 27cae2ecc1..54b167aaf3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -31,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks new IssueTemplateOffscreenSlider(this) }; - public IEnumerable Run(IBeatmap beatmap) + public IEnumerable Run(WorkingBeatmap workingBeatmap) { - foreach (var hitobject in beatmap.HitObjects) + foreach (var hitobject in workingBeatmap.Beatmap.HitObjects) { switch (hitobject) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 1c7ab00bbb..9b9383d547 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Osu.Edit new CheckOffscreenObjects() }; - public IEnumerable Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap)); + public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); } } diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs index 635e3bb0f3..d61f0989a6 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Editing.Checks { @@ -14,13 +15,13 @@ namespace osu.Game.Tests.Editing.Checks public class CheckBackgroundTest { private CheckBackground check; - private IBeatmap beatmap; + private WorkingBeatmap beatmap; [SetUp] public void Setup() { check = new CheckBackground(); - beatmap = new Beatmap + beatmap = new TestWorkingBeatmap(new Beatmap { BeatmapInfo = new BeatmapInfo { @@ -33,7 +34,7 @@ namespace osu.Game.Tests.Editing.Checks }) } } - }; + }); } [Test] diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index f9bced7beb..40714e8c7e 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Edit new CheckBackground(), }; - public IEnumerable Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap)); + public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 93da42425c..d2fffeea4e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -18,9 +18,9 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateDoesNotExist(this) }; - public IEnumerable Run(IBeatmap beatmap) + public IEnumerable Run(WorkingBeatmap workingBeatmap) { - if (beatmap.Metadata.BackgroundFile == null) + if (workingBeatmap.Metadata?.BackgroundFile == null) { yield return new IssueTemplateNoneSet(this).Create(); @@ -29,13 +29,13 @@ namespace osu.Game.Rulesets.Edit.Checks // If the background is set, also make sure it still exists. - var set = beatmap.BeatmapInfo.BeatmapSet; - var file = set.Files.FirstOrDefault(f => f.Filename == beatmap.Metadata.BackgroundFile); + var set = workingBeatmap.BeatmapInfo.BeatmapSet; + var file = set.Files.FirstOrDefault(f => f.Filename == workingBeatmap.Metadata.BackgroundFile); if (file != null) yield break; - yield return new IssueTemplateDoesNotExist(this).Create(beatmap.Metadata.BackgroundFile); + yield return new IssueTemplateDoesNotExist(this).Create(workingBeatmap.Metadata.BackgroundFile); } public class IssueTemplateNoneSet : IssueTemplate diff --git a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs index f284240092..a2814ee603 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// Runs this check and returns any issues detected for the provided beatmap. /// - /// The beatmap to run the check on. - public IEnumerable Run(IBeatmap beatmap); + /// The beatmap to run the check on. + public IEnumerable Run(WorkingBeatmap workingBeatmap); } } diff --git a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs index 61d8119635..12be8815e1 100644 --- a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs @@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Edit /// public interface IBeatmapVerifier { - public IEnumerable Run(IBeatmap beatmap); + public IEnumerable Run(WorkingBeatmap workingBeatmap); } } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 550fbe2950..160a14caac 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -61,7 +62,7 @@ namespace osu.Game.Screens.Edit.Verify private EditorClock clock { get; set; } [Resolved] - protected EditorBeatmap Beatmap { get; private set; } + protected WorkingBeatmap Beatmap { get; private set; } [Resolved] private Bindable selectedIssue { get; set; } From 400f8b3938428d22ca25d5760214045f42b3328d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 17 Apr 2021 17:47:13 +0200 Subject: [PATCH 023/400] Add `GetStream` to `IWorkingBeatmap` This is necessary to obtain the filesize of the audio and background files. --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 2 ++ osu.Game.Tests/WaveformTestBeatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapManager.cs | 1 + osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 3 +++ osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 3 +++ osu.Game/Beatmaps/WorkingBeatmap.cs | 3 +++ osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 2 ++ osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 2 ++ osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 3 +++ 9 files changed, 21 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 920cc36776..a18f82fe4a 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -168,6 +168,8 @@ namespace osu.Game.Tests.Beatmaps.Formats protected override Texture GetBackground() => throw new NotImplementedException(); protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); } } } diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 8c8c827404..cbed28641c 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -52,6 +52,8 @@ namespace osu.Game.Tests protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile)); + public override Stream GetStream(string storagePath) => null; + protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile); private string firstAudioFile diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b4ea898b7d..5e975de77c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -526,6 +526,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; protected override Track GetBeatmapTrack() => null; + public override Stream GetStream(string storagePath) => null; } } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 62cf29dc03..c1fc7a72e2 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.IO; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; @@ -142,6 +143,8 @@ namespace osu.Game.Beatmaps return null; } } + + public override Stream GetStream(string storagePath) => resources.Files.GetStream(storagePath); } } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index c114358771..6922d1c286 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using JetBrains.Annotations; using osu.Framework.Audio; @@ -48,6 +49,8 @@ namespace osu.Game.Beatmaps protected override Track GetBeatmapTrack() => GetVirtualTrack(); + public override Stream GetStream(string storagePath) => null; + private class DummyRulesetInfo : RulesetInfo { public override Ruleset CreateInstance() => new DummyRuleset(); diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index f7f276230f..e0eeaf6db0 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -326,6 +327,8 @@ namespace osu.Game.Beatmaps protected virtual ISkin GetSkin() => new DefaultSkin(); private readonly RecyclableLazy skin; + public abstract Stream GetStream(string storagePath); + public class RecyclableLazy { private Lazy lazy; diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index f2e0320ce3..66784fda54 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -116,6 +116,8 @@ namespace osu.Game.Screens.Edit protected override Texture GetBackground() => throw new NotImplementedException(); protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); } } } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 5ef2458919..1c5e551042 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -215,6 +215,8 @@ namespace osu.Game.Tests.Beatmaps protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); + protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) { var converter = base.CreateBeatmapConverter(beatmap, ruleset); diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index bfcb2403c1..852006bc9b 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.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.IO; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; @@ -35,6 +36,8 @@ namespace osu.Game.Tests.Beatmaps protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard(); + public override Stream GetStream(string storagePath) => null; + protected override Texture GetBackground() => null; protected override Track GetBeatmapTrack() => null; From b36da2664c2be2e9be6a2c69383dc3b95d459080 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 17 Apr 2021 17:49:10 +0200 Subject: [PATCH 024/400] Add `GetPathForFile` to `BeatmapSetInfo` This is used in several places, and so should probably have a function rather than remaining as duplicated code. Also applies this together with the previous commit to `BeatmapManagerWorkingBeatmap`. --- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 15 ++++++--------- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 ++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index c1fc7a72e2..d78ffbbfb6 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.IO; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; @@ -37,7 +36,7 @@ namespace osu.Game.Beatmaps try { - using (var stream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapInfo.Path)))) + using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) return Decoder.GetDecoder(stream).Decode(stream); } catch (Exception e) @@ -47,8 +46,6 @@ namespace osu.Game.Beatmaps } } - private string getPathForFile(string filename) => BeatmapSetInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes. protected override Texture GetBackground() @@ -58,7 +55,7 @@ namespace osu.Game.Beatmaps try { - return resources.LargeTextureStore.Get(getPathForFile(Metadata.BackgroundFile)); + return resources.LargeTextureStore.Get(BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile)); } catch (Exception e) { @@ -74,7 +71,7 @@ namespace osu.Game.Beatmaps try { - return resources.Tracks.Get(getPathForFile(Metadata.AudioFile)); + return resources.Tracks.Get(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile)); } catch (Exception e) { @@ -90,7 +87,7 @@ namespace osu.Game.Beatmaps try { - var trackData = resources.Files.GetStream(getPathForFile(Metadata.AudioFile)); + var trackData = GetStream(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile)); return trackData == null ? null : new Waveform(trackData); } catch (Exception e) @@ -106,7 +103,7 @@ namespace osu.Game.Beatmaps try { - using (var stream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapInfo.Path)))) + using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) { var decoder = Decoder.GetDecoder(stream); @@ -115,7 +112,7 @@ namespace osu.Game.Beatmaps storyboard = decoder.Decode(stream); else { - using (var secondaryStream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) + using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapSetInfo.StoryboardFile)))) storyboard = decoder.Decode(stream, secondaryStream); } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 7bc1c8c7b9..774bd0bc62 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -59,6 +59,8 @@ namespace osu.Game.Beatmaps public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + public string GetPathForFile(string filename) => Files?.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + public List Files { get; set; } public override string ToString() => Metadata?.ToString() ?? base.ToString(); From 62c54e00cb7eee3b424e965a09462431ab03cf9c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 17 Apr 2021 18:01:04 +0200 Subject: [PATCH 025/400] Add check for background resolution and filesize --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Edit/Checks/CheckBackgroundQuality.cs | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 40714e8c7e..24a4f473de 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Edit private readonly List checks = new List { new CheckBackground(), + new CheckBackgroundQuality() }; public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs new file mode 100644 index 0000000000..ed504f52ed --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -0,0 +1,98 @@ +// 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.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckBackgroundQuality : ICheck + { + // These are the requirements as stated in the Ranking Criteria. + // See https://osu.ppy.sh/wiki/en/Ranking_Criteria#rules.5 + private const int min_width = 160; + private const int max_width = 2560; + private const int min_height = 120; + private const int max_height = 1440; + private const double max_filesize_mb = 2.5d; + + // It's usually possible to find a higher resolution of the same image if lower than these. + private const int low_width = 960; + private const int low_height = 540; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Too high or low background resolution"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateTooHighResolution(this), + new IssueTemplateTooLowResolution(this), + new IssueTemplateTooUncompressed(this) + }; + + public IEnumerable Run(WorkingBeatmap workingBeatmap) + { + if (workingBeatmap.Metadata?.BackgroundFile == null) + yield break; + + var texture = workingBeatmap.Background; + if (texture == null) + yield break; + + if (texture.Width > max_width || texture.Height > max_height) + yield return new IssueTemplateTooHighResolution(this).Create(texture.Width, texture.Height); + + if (texture.Width < min_width || texture.Height < min_height) + yield return new IssueTemplateTooLowResolution(this).Create(texture.Width, texture.Height); + + if (texture.Width < low_width || texture.Height < low_height) + yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); + + string storagePath = workingBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(workingBeatmap.Metadata.BackgroundFile); + double filesizeMb = workingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); + + if (filesizeMb > max_filesize_mb) + yield return new IssueTemplateTooUncompressed(this).Create(filesizeMb); + } + + public class IssueTemplateTooHighResolution : IssueTemplate + { + public IssueTemplateTooHighResolution(ICheck check) + : base(check, IssueType.Problem, "The background resolution ({0} x {1}) exceeds {2} x {3}.") + { + } + + public Issue Create(double width, double height) => new Issue(this, width, height, max_width, max_height); + } + + public class IssueTemplateTooLowResolution : IssueTemplate + { + public IssueTemplateTooLowResolution(ICheck check) + : base(check, IssueType.Problem, "The background resolution ({0} x {1}) is lower than {2} x {3}.") + { + } + + public Issue Create(double width, double height) => new Issue(this, width, height, min_width, min_height); + } + + public class IssueTemplateLowResolution : IssueTemplate + { + public IssueTemplateLowResolution(ICheck check) + : base(check, IssueType.Warning, "The background resolution ({0} x {1}) is lower than {2} x {3}.") + { + } + + public Issue Create(double width, double height) => new Issue(this, width, height, low_width, low_height); + } + + public class IssueTemplateTooUncompressed : IssueTemplate + { + public IssueTemplateTooUncompressed(ICheck check) + : base(check, IssueType.Problem, "The background filesize ({0:0.#} MB) exceeds {1} MB.") + { + } + + public Issue Create(double actualMb) => new Issue(this, actualMb, max_filesize_mb); + } + } +} From 81be562379d5bb60023be3f20ecb1f8990993ef9 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 12:28:31 -0400 Subject: [PATCH 026/400] Read StoryboardEndTime directly from Beatmap --- osu.Game/Screens/Play/GameplayClockContainer.cs | 5 ----- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 95419c0b35..ddbb087962 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -228,11 +228,6 @@ namespace osu.Game.Screens.Play adjustableClock.ChangeSource(track); } - /// - /// Gets the endtime of the last element in the storyboard in ms, or start time of the last hitobject if there's no storyboard. - /// - public double StoryboardEndTime => beatmap.Storyboard.LatestEventTime ?? 0; - protected override void Update() { if (!IsPaused.Value) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7ad9c1a8af..e998f33cc1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -366,7 +366,7 @@ namespace osu.Game.Screens.Play { RequestSkip = performUserRequestedSkip }, - skipOutroOverlay = new SkipOverlay(GameplayClockContainer.StoryboardEndTime) + skipOutroOverlay = new SkipOverlay(Beatmap.Value.Storyboard.LatestEventTime ?? 0) { RequestSkip = scheduleCompletion }, From 5a015290b9721e151a689abd29162d2deab1ad18 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 12:34:38 -0400 Subject: [PATCH 027/400] Add remarks back to LatestEventTime --- osu.Game/Storyboards/Storyboard.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 4fa64b7c34..0b02a4921b 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -40,6 +40,10 @@ namespace osu.Game.Storyboards /// Across all layers, find the latest point in time that a storyboard element ends at. /// Will return null if there are no elements. /// + /// + /// This iterates all elements and as such should be used sparingly or stored locally. + /// Videos and samples return StartTime as their EndTIme. + /// public double? LatestEventTime => Layers.SelectMany(l => l.Elements.OfType()).OrderByDescending(e => e.EndTime).FirstOrDefault()?.EndTime; /// From 7f5b1e84a16d42763f20ecb69359213e4d6868c9 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 13:57:32 -0400 Subject: [PATCH 028/400] Update TestSceneStoryboardWithOutro.cs - Construct storyboard in CreateWorkingBeatmap() - Use GameplayClockContainer.GameplayClock.CurrentTime - Remove unnecessary lines --- .../Gameplay/TestSceneStoryboardWithOutro.cs | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 27d203d5d6..16474d23ea 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; @@ -13,7 +12,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Scoring; -using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Storyboards; using osuTK; @@ -24,28 +22,14 @@ namespace osu.Game.Tests.Visual.Gameplay { protected new OutroPlayer Player => (OutroPlayer)base.Player; - private Storyboard storyboard; - private const double storyboard_duration = 2000; - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - config.SetValue(OsuSetting.ShowStoryboard, true); - storyboard = new Storyboard(); - var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.TimelineGroup.Alpha.Add(Easing.None, 0, storyboard_duration, 1, 0); - storyboard.GetLayer("Background").Add(sprite); - } - [SetUpSteps] public override void SetUpSteps() { base.SetUpSteps(); - AddStep("ignore user settings", () => - { - Player.DimmableStoryboard.IgnoreUserSettings.Value = true; - }); + AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true)); + AddStep("set dim level to 0", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0)); } [Test] @@ -61,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestStoryboardNoSkipOutro() { AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); - AddUntilStep("storyboard ends", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= storyboard_duration); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= storyboard_duration); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } @@ -87,8 +71,18 @@ namespace osu.Game.Tests.Visual.Gameplay return beatmap; } - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => - new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio); + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + { + if (storyboard == null) + { + storyboard = new Storyboard(); + var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + sprite.TimelineGroup.Alpha.Add(Easing.None, 0, storyboard_duration, 1, 0); + storyboard.GetLayer("Background").Add(sprite); + } + + return base.CreateWorkingBeatmap(beatmap, storyboard); + } protected class OutroPlayer : TestPlayer { @@ -96,8 +90,6 @@ namespace osu.Game.Tests.Visual.Gameplay public bool IsScoreShown => !this.IsCurrentScreen() && this.GetChildScreen() is ResultsScreen; - public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; - public OutroPlayer() : base(false) { @@ -105,7 +97,6 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Task ImportScore(Score score) { - // avoid database errors from trying to store the score return Task.CompletedTask; } } From cb41c89935a4e7b0e76865efd6e8dd36ceb22124 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 17 Apr 2021 20:10:07 +0200 Subject: [PATCH 029/400] Don't return low res and too low res at the same time --- osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index ed504f52ed..a3f363554e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -44,8 +44,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (texture.Width < min_width || texture.Height < min_height) yield return new IssueTemplateTooLowResolution(this).Create(texture.Width, texture.Height); - - if (texture.Width < low_width || texture.Height < low_height) + else if (texture.Width < low_width || texture.Height < low_height) yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); string storagePath = workingBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(workingBeatmap.Metadata.BackgroundFile); From e40cb6797df3896d6179dc0f85edef8168c5ee09 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 15:27:22 -0400 Subject: [PATCH 030/400] Use GetEndTime() to get storyboard endtime --- osu.Game/Storyboards/IStoryboardElement.cs | 13 +++++++++++++ .../Storyboards/IStoryboardElementHasDuration.cs | 3 +++ osu.Game/Storyboards/Storyboard.cs | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/IStoryboardElement.cs b/osu.Game/Storyboards/IStoryboardElement.cs index c4c150a8a4..377ad57ab2 100644 --- a/osu.Game/Storyboards/IStoryboardElement.cs +++ b/osu.Game/Storyboards/IStoryboardElement.cs @@ -14,4 +14,17 @@ namespace osu.Game.Storyboards Drawable CreateDrawable(); } + + public static class StoryboardElementExtensions + { + /// + /// Returns the end time of this object. + /// + /// + /// This returns the where available, falling back to otherwise. + /// + /// The object. + /// The end time of this object. + public static double GetEndTime(this IStoryboardElement storyboardElement) => (storyboardElement as IStoryboardElementHasDuration)?.EndTime ?? storyboardElement.StartTime; + } } diff --git a/osu.Game/Storyboards/IStoryboardElementHasDuration.cs b/osu.Game/Storyboards/IStoryboardElementHasDuration.cs index e6ee373812..daa0c55704 100644 --- a/osu.Game/Storyboards/IStoryboardElementHasDuration.cs +++ b/osu.Game/Storyboards/IStoryboardElementHasDuration.cs @@ -3,6 +3,9 @@ namespace osu.Game.Storyboards { + /// + /// A StoryboardElement that ends at a different time than its start time. + /// public interface IStoryboardElementHasDuration { double EndTime { get; } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 0b02a4921b..bc61f704dd 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -44,7 +44,7 @@ namespace osu.Game.Storyboards /// This iterates all elements and as such should be used sparingly or stored locally. /// Videos and samples return StartTime as their EndTIme. /// - public double? LatestEventTime => Layers.SelectMany(l => l.Elements.OfType()).OrderByDescending(e => e.EndTime).FirstOrDefault()?.EndTime; + public double? LatestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.GetEndTime()).LastOrDefault()?.GetEndTime(); /// /// Depth of the currently front-most storyboard layer, excluding the overlay layer. From fdcb5e924c9153de2065731689d902d887652bca Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 17:45:38 -0400 Subject: [PATCH 031/400] Initialize skipOutroOverlay with alpha 0, other small changes --- osu.Game/Screens/Play/Player.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e998f33cc1..ef0caa2fa3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -247,6 +247,7 @@ namespace osu.Game.Screens.Play HUDOverlay.ShowHud.Disabled = true; BreakOverlay.Hide(); skipIntroOverlay.Hide(); + skipOutroOverlay.Hide(); } DrawableRuleset.FrameStableClock.WaitingOnFrames.BindValueChanged(waiting => @@ -368,7 +369,8 @@ namespace osu.Game.Screens.Play }, skipOutroOverlay = new SkipOverlay(Beatmap.Value.Storyboard.LatestEventTime ?? 0) { - RequestSkip = scheduleCompletion + RequestSkip = scheduleCompletion, + Alpha = 0 }, FailOverlay = new FailOverlay { @@ -416,8 +418,6 @@ namespace osu.Game.Screens.Play }); } - skipOutroOverlay.Hide(); - return container; } @@ -539,13 +539,9 @@ namespace osu.Game.Screens.Play return; } - // show the score if in storyboard outro (score has been set) - bool scoreReady = prepareScoreForDisplayTask != null && prepareScoreForDisplayTask.IsCompleted; - - if (scoreReady) - { + // if the score is ready for display but results screen has not been pushed yet (e.g. storyboard is still playing beyond gameplay), then transition to results screen instead of exiting. + if (prepareScoreForDisplayTask != null) scheduleCompletion(); - } } this.Exit(); @@ -639,7 +635,6 @@ namespace osu.Game.Screens.Play if (storyboardHasOutro) { skipOutroOverlay.Show(); - completionProgressDelegate = null; return; } From eec77b052790f720fa716d10b96aad540b9cb083 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 17 Apr 2021 23:55:39 +0200 Subject: [PATCH 032/400] replace icon --- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 4e34cbe9e2..4c46e24f1b 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override string Acronym => "FF"; public override string Description => "The fruits are... floating?"; public override double ScoreMultiplier => 1; - public override IconUsage? Icon => FontAwesome.Brands.Fly; + public override IconUsage? Icon => FontAwesome.Solid.Cloud; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From 97bacbdc760942ccb7052441b249f6a1c87ce2bd Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 17:55:46 -0400 Subject: [PATCH 033/400] Show score after the end of the storyboard after it was toggled --- osu.Game/Screens/Play/DimmableStoryboard.cs | 3 ++- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index bebde1b15b..73b4153601 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -50,6 +50,7 @@ namespace osu.Game.Screens.Play return; drawableStoryboard = storyboard.CreateDrawable(); + HasStoryboardEnded.BindTo(drawableStoryboard.HasStoryboardEnded); if (async) LoadComponentAsync(drawableStoryboard, onStoryboardCreated); @@ -63,6 +64,6 @@ namespace osu.Game.Screens.Play OverlayLayerContainer.Add(storyboard.OverlayLayer.CreateProxy()); } - public IBindable HasStoryboardEnded => drawableStoryboard?.HasStoryboardEnded; + public IBindable HasStoryboardEnded = new BindableBool(true); } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 08c2bf1b4a..abfb4598ec 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -95,15 +95,13 @@ namespace osu.Game.Storyboards.Drawables /// public IBindable HasStoryboardEnded => hasStoryboardEnded; - private readonly BindableBool hasStoryboardEnded = new BindableBool(true); + private readonly BindableBool hasStoryboardEnded = new BindableBool(); private void updateHasStoryboardEnded() { - if (Storyboard.LatestEventTime == null) - return; - - var time = Clock.CurrentTime; - hasStoryboardEnded.Value = time >= Storyboard.LatestEventTime; + hasStoryboardEnded.Value = + Storyboard.LatestEventTime == null || + Time.Current >= Storyboard.LatestEventTime; } } } From bf8789528ae81cd332416ab0ed11572c53693a4a Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 01:13:57 +0200 Subject: [PATCH 034/400] Add `GetStream` to `IWorkingBeatmap` --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index bcd94d76fd..f1bf6f48ef 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets; @@ -67,5 +68,7 @@ namespace osu.Game.Beatmaps /// /// A fresh track instance, which will also be available via . Track LoadTrack(); + + Stream GetStream(string storagePath); } } From ef65c8910f0a6a9d76327810c70733630aed8c25 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 01:15:13 +0200 Subject: [PATCH 035/400] Fix resolved fields --- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 160a14caac..6aa479b38d 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -62,7 +62,10 @@ namespace osu.Game.Screens.Edit.Verify private EditorClock clock { get; set; } [Resolved] - protected WorkingBeatmap Beatmap { get; private set; } + private BeatmapManager beatmapManager { get; set; } + + [Resolved] + private EditorBeatmap beatmap { get; set; } [Resolved] private Bindable selectedIssue { get; set; } @@ -74,7 +77,7 @@ namespace osu.Game.Screens.Edit.Verify private void load(OsuColour colours) { generalVerifier = new BeatmapVerifier(); - rulesetVerifier = Beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); + rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); RelativeSizeAxes = Axes.Both; @@ -120,10 +123,11 @@ namespace osu.Game.Screens.Edit.Verify private void refresh() { - var issues = generalVerifier.Run(Beatmap); + var workingBeatmap = beatmapManager.GetWorkingBeatmap(beatmap.BeatmapInfo); + var issues = generalVerifier.Run(workingBeatmap); if (rulesetVerifier != null) - issues = issues.Concat(rulesetVerifier.Run(Beatmap)); + issues = issues.Concat(rulesetVerifier.Run(workingBeatmap)); table.Issues = issues .OrderBy(issue => issue.Template.Type) From abf512532e2f8d7e79d8b8fafe419b651fb1f896 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 01:19:25 +0200 Subject: [PATCH 036/400] Clean up check logic Makes use of the new `BeatmapSet.GetPathForFile` method and removes dependency on `WorkingBeatmap` specifically, allowing us to switch to `IWorkingBeatmap` later. --- osu.Game/Rulesets/Edit/Checks/CheckBackground.cs | 14 ++++++-------- .../Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 5 +++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index d2fffeea4e..637e603e17 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit.Checks.Components; @@ -20,7 +19,9 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(WorkingBeatmap workingBeatmap) { - if (workingBeatmap.Metadata?.BackgroundFile == null) + string backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; + + if (backgroundFile == null) { yield return new IssueTemplateNoneSet(this).Create(); @@ -28,14 +29,11 @@ namespace osu.Game.Rulesets.Edit.Checks } // If the background is set, also make sure it still exists. - - var set = workingBeatmap.BeatmapInfo.BeatmapSet; - var file = set.Files.FirstOrDefault(f => f.Filename == workingBeatmap.Metadata.BackgroundFile); - - if (file != null) + var storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); + if (storagePath != null) yield break; - yield return new IssueTemplateDoesNotExist(this).Create(workingBeatmap.Metadata.BackgroundFile); + yield return new IssueTemplateDoesNotExist(this).Create(backgroundFile); } public class IssueTemplateNoneSet : IssueTemplate diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index a3f363554e..48d536079a 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(WorkingBeatmap workingBeatmap) { - if (workingBeatmap.Metadata?.BackgroundFile == null) + var backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; + if (backgroundFile == null) yield break; var texture = workingBeatmap.Background; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Edit.Checks else if (texture.Width < low_width || texture.Height < low_height) yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); - string storagePath = workingBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(workingBeatmap.Metadata.BackgroundFile); + string storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); double filesizeMb = workingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); if (filesizeMb > max_filesize_mb) From 56bf49c85c525547c6ebe8bb1656b69e1cd2b0ec Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 01:21:20 +0200 Subject: [PATCH 037/400] Take `IWorkingBeatmap` instead of `WorkingBeatmap` This makes testing much easier, and allows for checking of any class deriving from that interface, including `WorkingBeatmap`. --- osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckBackground.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 2 +- osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index 54b167aaf3..c210f73b5f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks new IssueTemplateOffscreenSlider(this) }; - public IEnumerable Run(WorkingBeatmap workingBeatmap) + public IEnumerable Run(IWorkingBeatmap workingBeatmap) { foreach (var hitobject in workingBeatmap.Beatmap.HitObjects) { diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 637e603e17..71821b8073 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateDoesNotExist(this) }; - public IEnumerable Run(WorkingBeatmap workingBeatmap) + public IEnumerable Run(IWorkingBeatmap workingBeatmap) { string backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 48d536079a..1494ae5da4 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateTooUncompressed(this) }; - public IEnumerable Run(WorkingBeatmap workingBeatmap) + public IEnumerable Run(IWorkingBeatmap workingBeatmap) { var backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; if (backgroundFile == null) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs index a2814ee603..c3a64b58e9 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs @@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// Runs this check and returns any issues detected for the provided beatmap. /// /// The beatmap to run the check on. - public IEnumerable Run(WorkingBeatmap workingBeatmap); + public IEnumerable Run(IWorkingBeatmap workingBeatmap); } } From 0502fbb4296e103d4841ce86becb57aef38e73a1 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 01:21:51 +0200 Subject: [PATCH 038/400] Add background quality check tests --- .../Checks/CheckBackgroundQualityTest.cs | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs new file mode 100644 index 0000000000..8dad5bbf34 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -0,0 +1,130 @@ +// 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.IO; +using System.Linq; +using JetBrains.Annotations; +using Moq; +using NUnit.Framework; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using FileInfo = osu.Game.IO.FileInfo; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckBackgroundQualityTest + { + private CheckBackgroundQuality check; + private IBeatmap beatmap; + + [SetUp] + public void Setup() + { + check = new CheckBackgroundQuality(); + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" }, + BeatmapSet = new BeatmapSetInfo + { + Files = new List(new[] + { + new BeatmapSetFileInfo + { + Filename = "abc123.jpg", + FileInfo = new FileInfo + { + Hash = "abcdef" + } + } + }) + } + } + }; + } + + [Test] + public void TestBackgroundMissing() + { + // While this is a problem, it is out of scope for this check and is caught by a different one. + beatmap.Metadata.BackgroundFile = null; + var mock = getMockWorkingBeatmap(null, System.Array.Empty()); + + Assert.That(check.Run(mock.Object), Is.Empty); + } + + [Test] + public void TestBackgroundAcceptable() + { + var mock = getMockWorkingBeatmap(new Texture(1920, 1080)); + + Assert.That(check.Run(mock.Object), Is.Empty); + } + + [Test] + public void TestBackgroundTooHighResolution() + { + var mock = getMockWorkingBeatmap(new Texture(3840, 2160)); + + var issues = check.Run(mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooHighResolution); + } + + [Test] + public void TestBackgroundLowResolution() + { + var mock = getMockWorkingBeatmap(new Texture(640, 480)); + + var issues = check.Run(mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateLowResolution); + } + + [Test] + public void TestBackgroundTooLowResolution() + { + var mock = getMockWorkingBeatmap(new Texture(100, 100)); + + var issues = check.Run(mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooLowResolution); + } + + [Test] + public void TestBackgroundTooUncompressed() + { + var mock = getMockWorkingBeatmap(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); + + var issues = check.Run(mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed); + } + + /// + /// Returns the mock of the working beatmap with the given background and filesize. + /// + /// The texture of the background. + /// The bytes that represent the background file. + private Mock getMockWorkingBeatmap(Texture background, [CanBeNull] byte[] fileBytes = null) + { + var stream = new MemoryStream(fileBytes ?? new byte[1024 * 1024]); + + var mock = new Mock(); + mock.SetupGet(_ => _.Beatmap).Returns(beatmap); + mock.SetupGet(_ => _.Background).Returns(background); + mock.Setup(_ => _.GetStream(It.IsAny())).Returns(stream); + + return mock; + } + } +} From 010720de74c67df74f140aa2263cb85c4054b563 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 02:07:33 +0200 Subject: [PATCH 039/400] Factor out general file presence checking This allows us to use the same method of checking for other files that should exist, for example the audio file. By using the same method, they all share test cases too. --- ...groundTest.cs => CheckFilePresenceTest.cs} | 10 +++---- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 7 +++-- .../Edit/Checks/CheckBackgroundPresence.cs | 15 ++++++++++ ...heckBackground.cs => CheckFilePresence.cs} | 28 +++++++++++-------- 4 files changed, 41 insertions(+), 19 deletions(-) rename osu.Game.Tests/Editing/Checks/{CheckBackgroundTest.cs => CheckFilePresenceTest.cs} (84%) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs rename osu.Game/Rulesets/Edit/Checks/{CheckBackground.cs => CheckFilePresence.cs} (57%) diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs similarity index 84% rename from osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs rename to osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index d61f0989a6..1149115b55 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -12,15 +12,15 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Editing.Checks { [TestFixture] - public class CheckBackgroundTest + public class CheckFilePresenceTest { - private CheckBackground check; + private CheckBackgroundPresence check; private WorkingBeatmap beatmap; [SetUp] public void Setup() { - check = new CheckBackground(); + check = new CheckBackgroundPresence(); beatmap = new TestWorkingBeatmap(new Beatmap { BeatmapInfo = new BeatmapInfo @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(beatmap).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); - Assert.That(issues.Single().Template is CheckBackground.IssueTemplateDoesNotExist); + Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateDoesNotExist); } [Test] @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(beatmap).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); - Assert.That(issues.Single().Template is CheckBackground.IssueTemplateNoneSet); + Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateNoneSet); } } } diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 24a4f473de..90447049f8 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -16,8 +16,11 @@ namespace osu.Game.Rulesets.Edit { private readonly List checks = new List { - new CheckBackground(), - new CheckBackgroundQuality() + // Resources + new CheckBackgroundPresence(), + new CheckBackgroundQuality(), + // Audio + new CheckAudioPresence(), }; public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs new file mode 100644 index 0000000000..3a229b889b --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.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.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckBackgroundPresence : CheckFilePresence + { + protected override CheckCategory Category => CheckCategory.Resources; + protected override string TypeOfFile => "background"; + protected override string GetFilename(IWorkingBeatmap workingBeatmap) => workingBeatmap.Beatmap.Metadata?.BackgroundFile; + } +} diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs similarity index 57% rename from osu.Game/Rulesets/Edit/Checks/CheckBackground.cs rename to osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index 71821b8073..37fa79568f 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs @@ -7,9 +7,13 @@ using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit.Checks { - public class CheckBackground : ICheck + public abstract class CheckFilePresence : ICheck { - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Missing background"); + protected abstract CheckCategory Category { get; } + protected abstract string TypeOfFile { get; } + protected abstract string GetFilename(IWorkingBeatmap workingBeatmap); + + public CheckMetadata Metadata => new CheckMetadata(Category, $"Missing {TypeOfFile}"); public IEnumerable PossibleTemplates => new IssueTemplate[] { @@ -19,41 +23,41 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(IWorkingBeatmap workingBeatmap) { - string backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; + var filename = GetFilename(workingBeatmap); - if (backgroundFile == null) + if (filename == null) { - yield return new IssueTemplateNoneSet(this).Create(); + yield return new IssueTemplateNoneSet(this).Create(TypeOfFile); yield break; } - // If the background is set, also make sure it still exists. - var storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); + // If the file is set, also make sure it still exists. + var storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); if (storagePath != null) yield break; - yield return new IssueTemplateDoesNotExist(this).Create(backgroundFile); + yield return new IssueTemplateDoesNotExist(this).Create(TypeOfFile, filename); } public class IssueTemplateNoneSet : IssueTemplate { public IssueTemplateNoneSet(ICheck check) - : base(check, IssueType.Problem, "No background has been set.") + : base(check, IssueType.Problem, "No {0} has been set.") { } - public Issue Create() => new Issue(this); + public Issue Create(string typeOfFile) => new Issue(this, typeOfFile); } public class IssueTemplateDoesNotExist : IssueTemplate { public IssueTemplateDoesNotExist(ICheck check) - : base(check, IssueType.Problem, "The background file \"{0}\" does not exist.") + : base(check, IssueType.Problem, "The {0} file \"{1}\" does not exist.") { } - public Issue Create(string filename) => new Issue(this, filename); + public Issue Create(string typeOfFile, string filename) => new Issue(this, typeOfFile, filename); } } } From 9a69ca34a6ec004e6b336826f5623cbcd1c33a35 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 02:07:57 +0200 Subject: [PATCH 040/400] Add audio presence check --- .../Rulesets/Edit/Checks/CheckAudioPresence.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs new file mode 100644 index 0000000000..55e53ef519 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.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.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckAudioPresence : CheckFilePresence + { + protected override CheckCategory Category => CheckCategory.Audio; + protected override string TypeOfFile => "audio"; + protected override string GetFilename(IWorkingBeatmap workingBeatmap) => workingBeatmap.Beatmap.Metadata?.AudioFile; + } +} From c77f838fb051a4edaf2de036438045d489c10db2 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 21:49:07 -0400 Subject: [PATCH 041/400] HasStoryboardEnded doesn't trigger updateCompletionState() Scores won't be shown prematurely if the storyboard ends before the playable portion of the beatmap. --- osu.Game/Screens/Play/Player.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ef0caa2fa3..622d99f078 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -284,14 +284,16 @@ namespace osu.Game.Screens.Play ScoreProcessor.RevertResult(r); }; + DimmableStoryboard.HasStoryboardEnded.ValueChanged += _ => + { + if (ScoreProcessor.HasCompleted.Value) + scheduleCompletion(); + }; + // Bind the judgement processors to ourselves ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState; HealthProcessor.Failed += onFail; - // Keep track of whether the storyboard ended after the playable portion - if (DimmableStoryboard.HasStoryboardEnded != null) - DimmableStoryboard.HasStoryboardEnded.ValueChanged += updateCompletionState; - foreach (var mod in Mods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); @@ -630,7 +632,7 @@ namespace osu.Game.Screens.Play return score.ScoreInfo; }); - var storyboardHasOutro = DimmableStoryboard.ContentDisplayed && (!DimmableStoryboard.HasStoryboardEnded?.Value ?? false); + var storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; if (storyboardHasOutro) { From e9f8fa64b88b49c50c2ac9284e8d048034269e4d Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 21:49:29 -0400 Subject: [PATCH 042/400] Added a test for toggling the storyboard after the map is loaded --- .../Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 16474d23ea..e86b7591c6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -58,6 +58,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("score shown", () => Player.IsScoreShown); } + [TestCase(false)] + [TestCase(true)] + public void TestStoryboardToggle(bool enabledAtBeginning) + { + AddStep($"{(enabledAtBeginning ? "enable" : "disable")} storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, enabledAtBeginning)); + AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); + AddStep($"toggle storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, !enabledAtBeginning)); + AddUntilStep("wait for score shown", () => Player.IsScoreShown); + } + protected override bool AllowFail => false; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); From 7b1d40db7de4fc6544ebc1f4070d600a024df488 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 22:13:28 -0400 Subject: [PATCH 043/400] Remove redundant string interpolation --- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index e86b7591c6..59d543d686 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep($"{(enabledAtBeginning ? "enable" : "disable")} storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, enabledAtBeginning)); AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); - AddStep($"toggle storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, !enabledAtBeginning)); + AddStep("toggle storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, !enabledAtBeginning)); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } From a73bae7a66889f22f8a321745baf25eafafd3efe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 18 Apr 2021 06:35:43 +0300 Subject: [PATCH 044/400] Schedule completion when storyboard has actually ended --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 622d99f078..cb8ebdcce7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -284,9 +284,9 @@ namespace osu.Game.Screens.Play ScoreProcessor.RevertResult(r); }; - DimmableStoryboard.HasStoryboardEnded.ValueChanged += _ => + DimmableStoryboard.HasStoryboardEnded.ValueChanged += storyboardEnded => { - if (ScoreProcessor.HasCompleted.Value) + if (storyboardEnded.NewValue && ScoreProcessor.HasCompleted.Value) scheduleCompletion(); }; From f6a09be62d4a88ec2f15a4040c3b1e1d9e0e2329 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 18 Apr 2021 06:35:54 +0300 Subject: [PATCH 045/400] Add further xmldoc --- osu.Game/Screens/Play/DimmableStoryboard.cs | 10 ++++++++-- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 2 +- osu.Game/Storyboards/IStoryboardElement.cs | 8 ++++---- osu.Game/Storyboards/IStoryboardElementHasDuration.cs | 5 ++++- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 73b4153601..f8cedddfbe 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -20,6 +20,14 @@ namespace osu.Game.Screens.Play private readonly Storyboard storyboard; private DrawableStoryboard drawableStoryboard; + /// + /// Whether the storyboard is considered finished. + /// + /// + /// This is true by default in here, until an actual drawable storyboard is loaded, in which case it'll bind to it. + /// + public IBindable HasStoryboardEnded = new BindableBool(true); + public DimmableStoryboard(Storyboard storyboard) { this.storyboard = storyboard; @@ -63,7 +71,5 @@ namespace osu.Game.Screens.Play Add(storyboard); OverlayLayerContainer.Add(storyboard.OverlayLayer.CreateProxy()); } - - public IBindable HasStoryboardEnded = new BindableBool(true); } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index abfb4598ec..a9a8b8a4ac 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -91,7 +91,7 @@ namespace osu.Game.Storyboards.Drawables } /// - /// Whether the storyboard has ended after the gameplay portion of the beatmap. + /// Whether the storyboard is considered finished. /// public IBindable HasStoryboardEnded => hasStoryboardEnded; diff --git a/osu.Game/Storyboards/IStoryboardElement.cs b/osu.Game/Storyboards/IStoryboardElement.cs index 377ad57ab2..2f05e92070 100644 --- a/osu.Game/Storyboards/IStoryboardElement.cs +++ b/osu.Game/Storyboards/IStoryboardElement.cs @@ -18,13 +18,13 @@ namespace osu.Game.Storyboards public static class StoryboardElementExtensions { /// - /// Returns the end time of this object. + /// Returns the end time of this storyboard element. /// /// /// This returns the where available, falling back to otherwise. /// - /// The object. - /// The end time of this object. - public static double GetEndTime(this IStoryboardElement storyboardElement) => (storyboardElement as IStoryboardElementHasDuration)?.EndTime ?? storyboardElement.StartTime; + /// The storyboard element. + /// The end time of this element. + public static double GetEndTime(this IStoryboardElement element) => (element as IStoryboardElementHasDuration)?.EndTime ?? element.StartTime; } } diff --git a/osu.Game/Storyboards/IStoryboardElementHasDuration.cs b/osu.Game/Storyboards/IStoryboardElementHasDuration.cs index daa0c55704..b8d3b66694 100644 --- a/osu.Game/Storyboards/IStoryboardElementHasDuration.cs +++ b/osu.Game/Storyboards/IStoryboardElementHasDuration.cs @@ -4,10 +4,13 @@ namespace osu.Game.Storyboards { /// - /// A StoryboardElement that ends at a different time than its start time. + /// A that ends at a different time than its start time. /// public interface IStoryboardElementHasDuration { + /// + /// The time at which the ends. + /// double EndTime { get; } } } From f45aed125903673a1eaa27484bce71c5026d61e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 18 Apr 2021 07:16:00 +0300 Subject: [PATCH 046/400] Remove new line between skip overlay fields --- osu.Game/Screens/Play/Player.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cb8ebdcce7..da877fcd22 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -105,7 +105,6 @@ namespace osu.Game.Screens.Play private BreakTracker breakTracker; private SkipOverlay skipIntroOverlay; - private SkipOverlay skipOutroOverlay; protected ScoreProcessor ScoreProcessor { get; private set; } From 1b2c43b92cab2badef24f03755e3d8b282cfc04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 19:29:04 +0200 Subject: [PATCH 047/400] Add basic structure of colour palette --- .../TestSceneLabelledColourPalette.cs | 49 +++++++++ .../Graphics/UserInterfaceV2/ColourDisplay.cs | 103 ++++++++++++++++++ .../Graphics/UserInterfaceV2/ColourPalette.cs | 67 ++++++++++++ .../UserInterfaceV2/LabelledColourPalette.cs | 20 ++++ 4 files changed, 239 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs new file mode 100644 index 0000000000..afe95a3745 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneLabelledColourPalette : OsuTestScene + { + private LabelledColourPalette component; + + [Test] + public void TestPalette([Values] bool hasDescription) => createColourPalette(hasDescription); + + private void createColourPalette(bool hasDescription = false) + { + AddStep("create component", () => + { + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + Child = component = new LabelledColourPalette + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + + component.Label = "a sample component"; + component.Description = hasDescription ? "this text describes the component" : string.Empty; + + component.Colours.AddRange(new[] + { + Color4.DarkRed, + Color4.Aquamarine, + Color4.Goldenrod, + Color4.Gainsboro + }); + }); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs new file mode 100644 index 0000000000..ef5be62a37 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.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 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.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class ColourDisplay : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + private Box fill; + private OsuSpriteText colourHexCode; + private OsuSpriteText colourName; + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private LocalisableString name; + + public LocalisableString ColourName + { + get => name; + set + { + if (name == value) + return; + + name = value; + + colourName.Text = name; + } + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Y; + Width = 100; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.X, + Height = 100, + Masking = true, + Children = new Drawable[] + { + fill = new Box + { + RelativeSizeAxes = Axes.Both + }, + colourHexCode = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(size: 12) + } + } + }, + colourName = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + current.BindValueChanged(_ => updateColour(), true); + } + + private void updateColour() + { + fill.Colour = current.Value; + colourHexCode.Text = current.Value.ToHex(); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs new file mode 100644 index 0000000000..8fa76a017c --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs @@ -0,0 +1,67 @@ +// 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 osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class ColourPalette : CompositeDrawable + { + public BindableList Colours { get; } = new BindableList(); + + private FillFlowContainer palette; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = palette = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Direction = FillDirection.Full + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Colours.BindCollectionChanged((_, __) => updatePalette(), true); + } + + private void updatePalette() + { + palette.Clear(); + + foreach (var item in Colours) + { + palette.Add(new ColourDisplay + { + Current = { Value = item } + }); + } + + reindexItems(); + } + + private void reindexItems() + { + int index = 1; + + foreach (var colour in palette) + { + colour.ColourName = $"Colour {index}"; + index += 1; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs new file mode 100644 index 0000000000..f6f2d92d01 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.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.Bindables; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class LabelledColourPalette : LabelledDrawable + { + public LabelledColourPalette() + : base(true) + { + } + + public BindableList Colours => Component.Colours; + + protected override ColourPalette CreateComponent() => new ColourPalette(); + } +} From 67c19df00050c9d0f6fef7f69e9dc98edb2f4c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 19:35:42 +0200 Subject: [PATCH 048/400] Add test coverage for adding/removing colours --- .../TestSceneLabelledColourPalette.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs index afe95a3745..77d313e6ee 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterfaceV2; using osuTK.Graphics; @@ -14,7 +15,17 @@ namespace osu.Game.Tests.Visual.UserInterface private LabelledColourPalette component; [Test] - public void TestPalette([Values] bool hasDescription) => createColourPalette(hasDescription); + public void TestPalette([Values] bool hasDescription) + { + createColourPalette(hasDescription); + + AddRepeatStep("add random colour", () => component.Colours.Add(randomColour()), 4); + AddRepeatStep("remove random colour", () => + { + if (component.Colours.Count > 0) + component.Colours.RemoveAt(RNG.Next(component.Colours.Count)); + }, 5); + } private void createColourPalette(bool hasDescription = false) { @@ -45,5 +56,11 @@ namespace osu.Game.Tests.Visual.UserInterface }); }); } + + private Color4 randomColour() => new Color4( + RNG.NextSingle(), + RNG.NextSingle(), + RNG.NextSingle(), + 1); } } From a8027d87b6c9bd31c895d1eb243fdf6af689567e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 19:46:54 +0200 Subject: [PATCH 049/400] Fix unreadable colour hex code text due to low contrast Logic is shared with the timeline blueprints which also have the same problem of displaying text on top of a combo colour. Slightly modified the formula. Seems to yield better results on a subjective check. --- .../Graphics/UserInterfaceV2/ColourDisplay.cs | 2 ++ .../Timeline/TimelineHitObjectBlueprint.cs | 6 ++--- osu.Game/Utils/ColourUtils.cs | 23 +++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Utils/ColourUtils.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs index ef5be62a37..5d9d2521cb 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -98,6 +99,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { fill.Colour = current.Value; colourHexCode.Text = current.Value.ToHex(); + colourHexCode.Colour = ColourUtils.ForegroundTextColourFor(current.Value); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 105e04d441..dc67009d08 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -158,10 +159,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline circle.Colour = comboColour; var col = circle.Colour.TopLeft.Linear; - float brightness = col.R + col.G + col.B; - - // decide the combo index colour based on brightness? - colouredComponents.Colour = OsuColour.Gray(brightness > 0.5f ? 0.2f : 0.9f); + colouredComponents.Colour = ColourUtils.ForegroundTextColourFor(col); } protected override void Update() diff --git a/osu.Game/Utils/ColourUtils.cs b/osu.Game/Utils/ColourUtils.cs new file mode 100644 index 0000000000..33f6f5981f --- /dev/null +++ b/osu.Game/Utils/ColourUtils.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.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Utils +{ + public static class ColourUtils + { + /// + /// Returns a foreground text colour that is supposed to contrast well on top of + /// the supplied . + /// + public static Color4 ForegroundTextColourFor(Color4 backgroundColour) + { + // formula taken from the RGB->YIQ conversions: https://en.wikipedia.org/wiki/YIQ + // brightness here is equivalent to the Y component in the above colour model, which is a rough estimate of lightness. + float brightness = 0.299f * backgroundColour.R + 0.587f * backgroundColour.G + 0.114f * backgroundColour.B; + return OsuColour.Gray(brightness > 0.5f ? 0.2f : 0.9f); + } + } +} From 0cd1aa8c1cca9a984968a6c84ae25a5b55ee2cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 19:56:03 +0200 Subject: [PATCH 050/400] Add support for custom colour prefixes --- .../TestSceneLabelledColourPalette.cs | 3 +++ .../Graphics/UserInterfaceV2/ColourPalette.cs | 19 ++++++++++++++++++- .../UserInterfaceV2/LabelledColourPalette.cs | 6 ++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs index 77d313e6ee..325e71f76a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs @@ -25,6 +25,8 @@ namespace osu.Game.Tests.Visual.UserInterface if (component.Colours.Count > 0) component.Colours.RemoveAt(RNG.Next(component.Colours.Count)); }, 5); + + AddStep("set custom prefix", () => component.ColourNamePrefix = "Combo"); } private void createColourPalette(bool hasDescription = false) @@ -41,6 +43,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, + ColourNamePrefix = "My colour #" } }; diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs index 8fa76a017c..38ac51b955 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs @@ -14,6 +14,23 @@ namespace osu.Game.Graphics.UserInterfaceV2 { public BindableList Colours { get; } = new BindableList(); + private string colourNamePrefix = "Colour"; + + public string ColourNamePrefix + { + get => colourNamePrefix; + set + { + if (colourNamePrefix == value) + return; + + colourNamePrefix = value; + + if (IsLoaded) + reindexItems(); + } + } + private FillFlowContainer palette; [BackgroundDependencyLoader] @@ -59,7 +76,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 foreach (var colour in palette) { - colour.ColourName = $"Colour {index}"; + colour.ColourName = $"{colourNamePrefix} {index}"; index += 1; } } diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs index f6f2d92d01..58443953bc 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs @@ -15,6 +15,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 public BindableList Colours => Component.Colours; + public string ColourNamePrefix + { + get => Component.ColourNamePrefix; + set => Component.ColourNamePrefix = value; + } + protected override ColourPalette CreateComponent() => new ColourPalette(); } } From 577755ee1943c65b4e26dfa7cef7c39a43896a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 20:15:30 +0200 Subject: [PATCH 051/400] Add placeholder when no colours are visible Will be removed once combo colours are mutable. --- .../TestSceneLabelledColourPalette.cs | 7 ++-- .../Graphics/UserInterfaceV2/ColourPalette.cs | 42 ++++++++++++++++--- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs index 325e71f76a..826da17ca8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs @@ -20,13 +20,14 @@ namespace osu.Game.Tests.Visual.UserInterface createColourPalette(hasDescription); AddRepeatStep("add random colour", () => component.Colours.Add(randomColour()), 4); + + AddStep("set custom prefix", () => component.ColourNamePrefix = "Combo"); + AddRepeatStep("remove random colour", () => { if (component.Colours.Count > 0) component.Colours.RemoveAt(RNG.Next(component.Colours.Count)); - }, 5); - - AddStep("set custom prefix", () => component.ColourNamePrefix = "Combo"); + }, 8); } private void createColourPalette(bool hasDescription = false) diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs index 38ac51b955..3ca5c2d8d1 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -32,6 +34,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 } private FillFlowContainer palette; + private Container placeholder; [BackgroundDependencyLoader] private void load() @@ -39,12 +42,27 @@ namespace osu.Game.Graphics.UserInterfaceV2 RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = palette = new FillFlowContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(10), - Direction = FillDirection.Full + palette = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Direction = FillDirection.Full + }, + placeholder = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = "(none)", + Font = OsuFont.Default.With(weight: FontWeight.Bold) + } + } }; } @@ -53,12 +71,26 @@ namespace osu.Game.Graphics.UserInterfaceV2 base.LoadComplete(); Colours.BindCollectionChanged((_, __) => updatePalette(), true); + FinishTransforms(true); } + private const int fade_duration = 200; + private void updatePalette() { palette.Clear(); + if (Colours.Any()) + { + palette.FadeIn(fade_duration, Easing.OutQuint); + placeholder.FadeOut(fade_duration, Easing.OutQuint); + } + else + { + palette.FadeOut(fade_duration, Easing.OutQuint); + placeholder.FadeIn(fade_duration, Easing.OutQuint); + } + foreach (var item in Colours) { palette.Add(new ColourDisplay From 07a00cd68130e973de37e68f119191c8bc971f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 20:16:17 +0200 Subject: [PATCH 052/400] Add colours section with combo colour display --- osu.Game/Screens/Edit/Setup/ColoursSection.cs | 37 +++++++++++++++++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 1 + 2 files changed, 38 insertions(+) create mode 100644 osu.Game/Screens/Edit/Setup/ColoursSection.cs diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs new file mode 100644 index 0000000000..91dfd74a78 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Setup +{ + internal class ColoursSection : SetupSection + { + public override LocalisableString Title => "Colours"; + + private LabelledColourPalette comboColours; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + comboColours = new LabelledColourPalette + { + Label = "Hitcircle / Slider Combos", + ColourNamePrefix = "Combo" + } + }; + + var colours = Beatmap.BeatmapSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; + if (colours != null) + comboColours.Colours.AddRange(colours); + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 70671b487c..dc81b31f65 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -61,6 +61,7 @@ namespace osu.Game.Screens.Edit.Setup new ResourcesSection(), new MetadataSection(), new DifficultySection(), + new ColoursSection() } }, } From 6f2ebb20a7fedc0d02037540b3610e7e64288239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 20:29:09 +0200 Subject: [PATCH 053/400] Fix test failing due to null beatmap skin Also annotate the field on `EditorBeatmap` as nullable for future travelers. --- osu.Game/Screens/Edit/EditorBeatmap.cs | 1 + osu.Game/Screens/Edit/Setup/ColoursSection.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 4f1b0484d2..4bf4a3b8f3 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -46,6 +46,7 @@ namespace osu.Game.Screens.Edit public readonly IBeatmap PlayableBeatmap; + [CanBeNull] public readonly ISkin BeatmapSkin; [Resolved] diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs index 91dfd74a78..cb7deadcb7 100644 --- a/osu.Game/Screens/Edit/Setup/ColoursSection.cs +++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Edit.Setup } }; - var colours = Beatmap.BeatmapSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; + var colours = Beatmap.BeatmapSkin?.GetConfig>(GlobalSkinColours.ComboColours)?.Value; if (colours != null) comboColours.Colours.AddRange(colours); } From 98460c8febc37c17acb949a864f528c32ef637cb Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sun, 18 Apr 2021 00:45:24 -0400 Subject: [PATCH 054/400] Rename IStoryboardElementHasDuration, remove unnecessary step in tests, add Duration field --- .../Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 4 ---- osu.Game/Storyboards/IStoryboardElement.cs | 4 ++-- ...entHasDuration.cs => IStoryboardElementWithDuration.cs} | 7 ++++++- osu.Game/Storyboards/StoryboardSprite.cs | 4 +++- 4 files changed, 11 insertions(+), 8 deletions(-) rename osu.Game/Storyboards/{IStoryboardElementHasDuration.cs => IStoryboardElementWithDuration.cs} (70%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 59d543d686..fa1c4aa0d1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -35,7 +35,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardSkipOutro() { - AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); AddAssert("score shown", () => Player.IsScoreShown); @@ -44,7 +43,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardNoSkipOutro() { - AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= storyboard_duration); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } @@ -52,7 +50,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardExitToSkipOutro() { - AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("exit via pause", () => Player.ExitViaPause()); AddAssert("score shown", () => Player.IsScoreShown); @@ -63,7 +60,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestStoryboardToggle(bool enabledAtBeginning) { AddStep($"{(enabledAtBeginning ? "enable" : "disable")} storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, enabledAtBeginning)); - AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); AddStep("toggle storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, !enabledAtBeginning)); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } diff --git a/osu.Game/Storyboards/IStoryboardElement.cs b/osu.Game/Storyboards/IStoryboardElement.cs index 2f05e92070..9a059991e6 100644 --- a/osu.Game/Storyboards/IStoryboardElement.cs +++ b/osu.Game/Storyboards/IStoryboardElement.cs @@ -21,10 +21,10 @@ namespace osu.Game.Storyboards /// Returns the end time of this storyboard element. /// /// - /// This returns the where available, falling back to otherwise. + /// This returns the where available, falling back to otherwise. /// /// The storyboard element. /// The end time of this element. - public static double GetEndTime(this IStoryboardElement element) => (element as IStoryboardElementHasDuration)?.EndTime ?? element.StartTime; + public static double GetEndTime(this IStoryboardElement element) => (element as IStoryboardElementWithDuration)?.EndTime ?? element.StartTime; } } diff --git a/osu.Game/Storyboards/IStoryboardElementHasDuration.cs b/osu.Game/Storyboards/IStoryboardElementWithDuration.cs similarity index 70% rename from osu.Game/Storyboards/IStoryboardElementHasDuration.cs rename to osu.Game/Storyboards/IStoryboardElementWithDuration.cs index b8d3b66694..02b438cb76 100644 --- a/osu.Game/Storyboards/IStoryboardElementHasDuration.cs +++ b/osu.Game/Storyboards/IStoryboardElementWithDuration.cs @@ -6,11 +6,16 @@ namespace osu.Game.Storyboards /// /// A that ends at a different time than its start time. /// - public interface IStoryboardElementHasDuration + public interface IStoryboardElementWithDuration : IStoryboardElement { /// /// The time at which the ends. /// double EndTime { get; } + + /// + /// The duration of the StoryboardElement. + /// + double Duration { get; } } } diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 51bdce3321..0aaf264341 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -11,7 +11,7 @@ using JetBrains.Annotations; namespace osu.Game.Storyboards { - public class StoryboardSprite : IStoryboardElement, IStoryboardElementHasDuration + public class StoryboardSprite : IStoryboardElementWithDuration { private readonly List loops = new List(); private readonly List triggers = new List(); @@ -65,6 +65,8 @@ namespace osu.Game.Storyboards } } + public double Duration => EndTime - StartTime; + public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands); private delegate void DrawablePropertyInitializer(Drawable drawable, T value); From 99fab456b57adb11d4bbb6a92b959633fb33c21c Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sun, 18 Apr 2021 23:25:20 -0400 Subject: [PATCH 055/400] Storyboard completion calls updateCompletionState MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - If the storyboard ends after the beatmap, call updateCompletionState as if the score processor has completed at that time. (completionProgressDelegate is null here since earlier when the score processor actually completed, updateCompletionState returned after showing the skip overlay.) - If the storyboard ends before the beatmap does, updateCompletionState simply returns and waits until the score processor is completed. - If the storyboard and beatmap end at the exact same time, make sure updateCompletionState() is called only once by the score processor completion. Co-Authored-By: Marlina José --- osu.Game/Screens/Play/Player.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index da877fcd22..231fcbc174 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -285,8 +285,8 @@ namespace osu.Game.Screens.Play DimmableStoryboard.HasStoryboardEnded.ValueChanged += storyboardEnded => { - if (storyboardEnded.NewValue && ScoreProcessor.HasCompleted.Value) - scheduleCompletion(); + if (storyboardEnded.NewValue && completionProgressDelegate == null) + updateCompletionState(new ValueChangedEvent(true, ScoreProcessor.HasCompleted.Value)); }; // Bind the judgement processors to ourselves From fd1241cc8513c72674f723a4c8767d9d5672430d Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sun, 18 Apr 2021 23:26:50 -0400 Subject: [PATCH 056/400] Added tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New tests: - storyboard ending during the failing animation - showResults = false - storyboard ending before score processor completion Co-Authored-By: Marlina José --- .../Gameplay/TestSceneStoryboardWithOutro.cs | 92 ++++++++++++++++--- 1 file changed, 78 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index fa1c4aa0d1..8326063f81 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -1,17 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Storyboards; using osuTK; @@ -20,9 +25,15 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneStoryboardWithOutro : PlayerTestScene { + protected override bool HasCustomSteps => true; + protected new OutroPlayer Player => (OutroPlayer)base.Player; - private const double storyboard_duration = 2000; + private double currentStoryboardDuration; + + private bool showResults = true; + + private event Func currentFailConditions; [SetUpSteps] public override void SetUpSteps() @@ -30,11 +41,15 @@ namespace osu.Game.Tests.Visual.Gameplay base.SetUpSteps(); AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true)); AddStep("set dim level to 0", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0)); + AddStep("reset fail conditions", () => currentFailConditions = (_, __) => false); + AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000); + AddStep("set ShowResults = true", () => showResults = true); } [Test] public void TestStoryboardSkipOutro() { + CreateTest(null); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); AddAssert("score shown", () => Player.IsScoreShown); @@ -43,13 +58,15 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardNoSkipOutro() { - AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= storyboard_duration); + CreateTest(null); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } [Test] public void TestStoryboardExitToSkipOutro() { + CreateTest(null); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("exit via pause", () => Player.ExitViaPause()); AddAssert("score shown", () => Player.IsScoreShown); @@ -59,16 +76,51 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(true)] public void TestStoryboardToggle(bool enabledAtBeginning) { + CreateTest(null); AddStep($"{(enabledAtBeginning ? "enable" : "disable")} storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, enabledAtBeginning)); AddStep("toggle storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, !enabledAtBeginning)); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } - protected override bool AllowFail => false; + [Test] + public void TestOutroEndsDuringFailAnimation() + { + CreateTest(() => + { + AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true); + AddStep("set storyboard duration to 1.3s", () => currentStoryboardDuration = 1300); + }); + AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); + AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible); + } + + [Test] + public void TestShowResultsFalse() + { + CreateTest(() => + { + AddStep("set ShowResults = false", () => showResults = false); + }); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); + AddWaitStep("wait", 10); + AddAssert("no score shown", () => !Player.IsScoreShown); + } + + [Test] + public void TestStoryboardEndsBeforeCompletion() + { + CreateTest(() => AddStep("set storyboard duration to .1s", () => currentStoryboardDuration = 100)); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); + AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + AddUntilStep("wait for score shown", () => Player.IsScoreShown); + } + + protected override bool AllowFail => true; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OutroPlayer(); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OutroPlayer(currentFailConditions, showResults); protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { @@ -79,26 +131,38 @@ namespace osu.Game.Tests.Visual.Gameplay protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { - if (storyboard == null) - { - storyboard = new Storyboard(); - var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); - sprite.TimelineGroup.Alpha.Add(Easing.None, 0, storyboard_duration, 1, 0); - storyboard.GetLayer("Background").Add(sprite); - } + return base.CreateWorkingBeatmap(beatmap, createStoryboard(currentStoryboardDuration)); + } - return base.CreateWorkingBeatmap(beatmap, storyboard); + private Storyboard createStoryboard(double duration) + { + var storyboard = new Storyboard(); + var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + sprite.TimelineGroup.Alpha.Add(Easing.None, 0, duration, 1, 0); + storyboard.GetLayer("Background").Add(sprite); + return storyboard; } protected class OutroPlayer : TestPlayer { public void ExitViaPause() => PerformExit(true); + public new FailOverlay FailOverlay => base.FailOverlay; + public bool IsScoreShown => !this.IsCurrentScreen() && this.GetChildScreen() is ResultsScreen; - public OutroPlayer() - : base(false) + private event Func failConditions; + + public OutroPlayer(Func failConditions, bool showResults = true) + : base(false, showResults) { + this.failConditions = failConditions; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + HealthProcessor.FailConditions += failConditions; } protected override Task ImportScore(Score score) From f9f514ffec646a245d8c310594c4bae619b4a0c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 12:37:56 +0900 Subject: [PATCH 057/400] Add basic xmldoc to show how the two colour classes interact --- osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs | 3 +++ osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs index 5d9d2521cb..c07e5d5bf7 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs @@ -16,6 +16,9 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { + /// + /// A component which displays a colour along with related description text. + /// public class ColourDisplay : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(); diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs index 3ca5c2d8d1..ba950048dc 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs @@ -12,6 +12,9 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { + /// + /// A component which displays a collection of colours in individual s. + /// public class ColourPalette : CompositeDrawable { public BindableList Colours { get; } = new BindableList(); From 0b36dd9bce6e4f958b51e91d52c7fd650abf2673 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 19 Apr 2021 01:23:21 -0400 Subject: [PATCH 058/400] Skip outro overlay and PerformExit() call updateCompletionState() instead of scheduleCompletion() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Marlina José --- osu.Game/Screens/Play/Player.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 231fcbc174..ef2e042260 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -290,7 +290,7 @@ namespace osu.Game.Screens.Play }; // Bind the judgement processors to ourselves - ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState; + ScoreProcessor.HasCompleted.BindValueChanged(e => updateCompletionState(e)); HealthProcessor.Failed += onFail; foreach (var mod in Mods.Value.OfType()) @@ -370,7 +370,7 @@ namespace osu.Game.Screens.Play }, skipOutroOverlay = new SkipOverlay(Beatmap.Value.Storyboard.LatestEventTime ?? 0) { - RequestSkip = scheduleCompletion, + RequestSkip = () => updateCompletionState(new ValueChangedEvent(true, true), true), Alpha = 0 }, FailOverlay = new FailOverlay @@ -542,7 +542,7 @@ namespace osu.Game.Screens.Play // if the score is ready for display but results screen has not been pushed yet (e.g. storyboard is still playing beyond gameplay), then transition to results screen instead of exiting. if (prepareScoreForDisplayTask != null) - scheduleCompletion(); + updateCompletionState(new ValueChangedEvent(true, true), true); } this.Exit(); @@ -581,7 +581,7 @@ namespace osu.Game.Screens.Play private ScheduledDelegate completionProgressDelegate; private Task prepareScoreForDisplayTask; - private void updateCompletionState(ValueChangedEvent completionState) + private void updateCompletionState(ValueChangedEvent completionState, bool skipStoryboardOutro = false) { // screen may be in the exiting transition phase. if (!this.IsCurrentScreen()) @@ -631,6 +631,12 @@ namespace osu.Game.Screens.Play return score.ScoreInfo; }); + if (skipStoryboardOutro) + { + scheduleCompletion(); + return; + } + var storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; if (storyboardHasOutro) From abfa6aec87047027380c54f75fcba502da3941df Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 19 Apr 2021 01:58:19 -0400 Subject: [PATCH 059/400] Remove completionState parameter --- osu.Game/Screens/Play/Player.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ef2e042260..dd7cf944f7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -286,11 +286,11 @@ namespace osu.Game.Screens.Play DimmableStoryboard.HasStoryboardEnded.ValueChanged += storyboardEnded => { if (storyboardEnded.NewValue && completionProgressDelegate == null) - updateCompletionState(new ValueChangedEvent(true, ScoreProcessor.HasCompleted.Value)); + updateCompletionState(); }; // Bind the judgement processors to ourselves - ScoreProcessor.HasCompleted.BindValueChanged(e => updateCompletionState(e)); + ScoreProcessor.HasCompleted.BindValueChanged(_ => updateCompletionState()); HealthProcessor.Failed += onFail; foreach (var mod in Mods.Value.OfType()) @@ -370,7 +370,7 @@ namespace osu.Game.Screens.Play }, skipOutroOverlay = new SkipOverlay(Beatmap.Value.Storyboard.LatestEventTime ?? 0) { - RequestSkip = () => updateCompletionState(new ValueChangedEvent(true, true), true), + RequestSkip = () => updateCompletionState(true), Alpha = 0 }, FailOverlay = new FailOverlay @@ -542,7 +542,7 @@ namespace osu.Game.Screens.Play // if the score is ready for display but results screen has not been pushed yet (e.g. storyboard is still playing beyond gameplay), then transition to results screen instead of exiting. if (prepareScoreForDisplayTask != null) - updateCompletionState(new ValueChangedEvent(true, true), true); + updateCompletionState(true); } this.Exit(); @@ -581,13 +581,13 @@ namespace osu.Game.Screens.Play private ScheduledDelegate completionProgressDelegate; private Task prepareScoreForDisplayTask; - private void updateCompletionState(ValueChangedEvent completionState, bool skipStoryboardOutro = false) + private void updateCompletionState(bool skipStoryboardOutro = false) { // screen may be in the exiting transition phase. if (!this.IsCurrentScreen()) return; - if (!completionState.NewValue) + if (!ScoreProcessor.HasCompleted.Value) { completionProgressDelegate?.Cancel(); completionProgressDelegate = null; From 5885c24e003bd02c2ad1d1d01756e2dc513b9850 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 16:00:56 +0900 Subject: [PATCH 060/400] Clear current user states on disconnect --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 4bbc420223..03387cfb9f 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -125,6 +125,7 @@ namespace osu.Game.Online.Spectator else { playingUsers.Clear(); + currentUserStates.Clear(); } }, true); } From de9e37857ee380e203668841daecd4c11ae1d839 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 16:06:40 +0900 Subject: [PATCH 061/400] Lock around playingUsers --- .../Spectator/SpectatorStreamingClient.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 03387cfb9f..10a707a4ce 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -47,6 +47,8 @@ namespace osu.Game.Online.Spectator private readonly BindableList playingUsers = new BindableList(); + private readonly Dictionary currentUserStates = new Dictionary(); + [CanBeNull] private IBeatmap currentBeatmap; @@ -60,7 +62,6 @@ namespace osu.Game.Online.Spectator private IBindable> currentMods { get; set; } private readonly SpectatorState currentState = new SpectatorState(); - private readonly Dictionary currentUserStates = new Dictionary(); private bool isPlaying; @@ -124,8 +125,11 @@ namespace osu.Game.Online.Spectator } else { - playingUsers.Clear(); - currentUserStates.Clear(); + lock (userLock) + { + playingUsers.Clear(); + currentUserStates.Clear(); + } } }, true); } @@ -133,11 +137,13 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state) { - if (!playingUsers.Contains(userId)) - playingUsers.Add(userId); - lock (userLock) + { + if (!playingUsers.Contains(userId)) + playingUsers.Add(userId); + currentUserStates[userId] = state; + } OnUserBeganPlaying?.Invoke(userId, state); @@ -146,10 +152,11 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserFinishedPlaying(int userId, SpectatorState state) { - playingUsers.Remove(userId); - lock (userLock) + { + playingUsers.Remove(userId); currentUserStates.Remove(userId); + } OnUserFinishedPlaying?.Invoke(userId, state); From 83716ddb089a2e9ac43026aa6440998ccbc0e6ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 16:07:00 +0900 Subject: [PATCH 062/400] Rename currentUserStates -> playingUserStates --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 10a707a4ce..9def009469 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online.Spectator private readonly BindableList playingUsers = new BindableList(); - private readonly Dictionary currentUserStates = new Dictionary(); + private readonly Dictionary playingUserStates = new Dictionary(); [CanBeNull] private IBeatmap currentBeatmap; @@ -128,7 +128,7 @@ namespace osu.Game.Online.Spectator lock (userLock) { playingUsers.Clear(); - currentUserStates.Clear(); + playingUserStates.Clear(); } } }, true); @@ -142,7 +142,7 @@ namespace osu.Game.Online.Spectator if (!playingUsers.Contains(userId)) playingUsers.Add(userId); - currentUserStates[userId] = state; + playingUserStates[userId] = state; } OnUserBeganPlaying?.Invoke(userId, state); @@ -155,7 +155,7 @@ namespace osu.Game.Online.Spectator lock (userLock) { playingUsers.Remove(userId); - currentUserStates.Remove(userId); + playingUserStates.Remove(userId); } OnUserFinishedPlaying?.Invoke(userId, state); @@ -298,7 +298,7 @@ namespace osu.Game.Online.Spectator lock (userLock) { - foreach (var (userId, state) in currentUserStates) + foreach (var (userId, state) in playingUserStates) callback(userId, state); } } From c50b526ba09fa2fe98f2d1d43d822a38dc8237f2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 16:48:55 +0900 Subject: [PATCH 063/400] Remove local state dictionary from SpectatorScreen --- .../Online/Spectator/SpectatorStreamingClient.cs | 12 ++++++++++++ osu.Game/Screens/Spectate/SpectatorScreen.cs | 14 +++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 9def009469..13b12d9add 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -284,6 +284,18 @@ namespace osu.Game.Online.Spectator lastSendTime = Time.Current; } + /// + /// Attempts to retrieve the for a currently-playing user. + /// + /// The user. + /// The current for the user, if they're playing. null if the user is not playing. + /// true if successful (the user is playing), false otherwise. + public bool TryGetPlayingUserState(int userId, out SpectatorState state) + { + lock (userLock) + return playingUserStates.TryGetValue(userId, out state); + } + /// /// Bind an action to with the option of running the bound action once immediately. /// diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 7be6c6183b..f554b15abf 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -44,7 +44,6 @@ namespace osu.Game.Screens.Spectate private readonly object stateLock = new object(); private readonly Dictionary userMap = new Dictionary(); - private readonly Dictionary spectatorStates = new Dictionary(); private readonly Dictionary gameplayStates = new Dictionary(); private IBindable> managerUpdated; @@ -107,9 +106,12 @@ namespace osu.Game.Screens.Spectate lock (stateLock) { - foreach (var (userId, state) in spectatorStates) + foreach (var (userId, _) in userMap) { - if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == state.BeatmapID)) + if (!spectatorClient.TryGetPlayingUserState(userId, out var userState)) + continue; + + if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID)) updateGameplayState(userId); } } @@ -125,7 +127,6 @@ namespace osu.Game.Screens.Spectate if (!userMap.ContainsKey(userId)) return; - spectatorStates[userId] = state; Schedule(() => OnUserStateChanged(userId, state)); updateGameplayState(userId); @@ -138,7 +139,10 @@ namespace osu.Game.Screens.Spectate { Debug.Assert(userMap.ContainsKey(userId)); - var spectatorState = spectatorStates[userId]; + // The user may have stopped playing. + if (!spectatorClient.TryGetPlayingUserState(userId, out var spectatorState)) + return; + var user = userMap[userId]; var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance(); From 5afdc3ff66f39eb34bc8359e715f6719da8c038a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Apr 2021 19:56:17 +0900 Subject: [PATCH 064/400] Make DHO application logic clearer with Entry/HitObject separation --- .../Objects/Drawables/DrawableHitObject.cs | 57 ++++++++++++++----- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 669e4cecbe..dfeb87de88 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -204,23 +204,27 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The controlling the lifetime of . public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry) { - free(); - - HitObject = hitObject ?? throw new InvalidOperationException($"Cannot apply a null {nameof(HitObject)}."); - - this.lifetimeEntry = lifetimeEntry; + if (hitObject == null) + throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); if (lifetimeEntry != null) { - // Transfer lifetime from the entry. - LifetimeStart = lifetimeEntry.LifetimeStart; - LifetimeEnd = lifetimeEntry.LifetimeEnd; - - // Copy any existing result from the entry (required for rewind / judgement revert). - Result = lifetimeEntry.Result; + applyEntry(lifetimeEntry); } else - LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + { + applyHitObject(hitObject); + + // Set default lifetime for a non-pooled DHO + LifetimeStart = hitObject.StartTime - InitialLifetimeOffset; + } + } + + private void applyHitObject([NotNull] HitObject hitObject) + { + freeHitObject(); + + HitObject = hitObject; // Ensure this DHO has a result. Result ??= CreateResult(HitObject.CreateJudgement()) @@ -281,10 +285,23 @@ namespace osu.Game.Rulesets.Objects.Drawables hasHitObjectApplied = true; } + private void applyEntry([NotNull] HitObjectLifetimeEntry entry) + { + freeEntry(); + + setLifetime(entry.LifetimeStart, entry.LifetimeEnd); + lifetimeEntry = entry; + + // Copy any existing result from the entry (required for rewind / judgement revert). + Result = entry.Result; + + applyHitObject(entry.HitObject); + } + /// /// Removes the currently applied /// - private void free() + private void freeHitObject() { if (!hasHitObjectApplied) return; @@ -322,13 +339,23 @@ namespace osu.Game.Rulesets.Objects.Drawables HitObject = null; ParentHitObject = null; Result = null; - lifetimeEntry = null; clearExistingStateTransforms(); hasHitObjectApplied = false; } + private void freeEntry() + { + freeHitObject(); + + if (lifetimeEntry == null) return; + + lifetimeEntry = null; + + setLifetime(double.MaxValue, double.MaxValue); + } + protected sealed override void FreeAfterUse() { base.FreeAfterUse(); @@ -337,7 +364,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (!IsInPool) return; - free(); + freeEntry(); } /// From c7183f92f79efe7855fb4b4defacb9224659e3e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 19:53:55 +0900 Subject: [PATCH 065/400] Rename Restart() -> Reset() --- osu.Game/Screens/Play/GameplayClockContainer.cs | 4 ++-- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 642ede5f1c..ee65384bc9 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -82,9 +82,9 @@ namespace osu.Game.Screens.Play public virtual void Stop() => IsPaused.Value = true; /// - /// Restarts gameplay. + /// Resets this and the source to an initial state ready for gameplay. /// - public virtual void Restart() + public virtual void Reset() { AdjustableSource.Seek(0); AdjustableSource.Stop(); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index db0aa23001..5ea50cbdc7 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -123,10 +123,10 @@ namespace osu.Game.Screens.Play userOffsetClock.ProcessFrame(); } - public override void Restart() + public override void Reset() { updateRate(); - base.Restart(); + base.Reset(); } /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 841f906b05..27a4fcc291 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -811,7 +811,7 @@ namespace osu.Game.Screens.Play if (GameplayClockContainer.GameplayClock.IsRunning) throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running"); - GameplayClockContainer.Restart(); + GameplayClockContainer.Reset(); } public override void OnSuspending(IScreen next) From acbf4580a412772b6be6d8e3d60446bbe35b9003 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 19:55:59 +0900 Subject: [PATCH 066/400] Only set initial source in Reset() --- osu.Game/Screens/Play/GameplayClockContainer.cs | 17 +++++++++++++++-- .../Play/MasterGameplayClockContainer.cs | 4 ++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ee65384bc9..3da28f3560 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -29,17 +29,22 @@ namespace osu.Game.Screens.Play /// protected readonly DecoupleableInterpolatingFramedClock AdjustableSource; + /// + /// The source clock. + /// + protected IClock SourceClock { get; private set; } + /// /// Creates a new . /// /// The source used for timing. protected GameplayClockContainer(IClock sourceClock) { + SourceClock = sourceClock; + RelativeSizeAxes = Axes.Both; AdjustableSource = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - AdjustableSource.ChangeSource(sourceClock); - IsPaused.BindValueChanged(OnIsPausedChanged); } @@ -86,6 +91,8 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { + ChangeSource(SourceClock); + AdjustableSource.Seek(0); AdjustableSource.Stop(); @@ -93,6 +100,12 @@ namespace osu.Game.Screens.Play Start(); } + /// + /// Changes the source clock. + /// + /// The new source. + protected void ChangeSource(IClock sourceClock) => AdjustableSource.ChangeSource(SourceClock = sourceClock); + protected override void Update() { if (!IsPaused.Value) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 5ea50cbdc7..f019e50b60 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play /// public const double MINIMUM_SKIP_TIME = 1000; - protected Track Track => (Track)AdjustableSource.Source; + protected Track Track => (Track)SourceClock; public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play public void StopUsingBeatmapClock() { removeSourceClockAdjustments(); - AdjustableSource.ChangeSource(new TrackVirtual(beatmap.Track.Length)); + ChangeSource(new TrackVirtual(beatmap.Track.Length)); } private bool speedAdjustmentsApplied; From 2c487ddb70bf4078422fba8e772e5e564e582d36 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Apr 2021 20:37:06 +0900 Subject: [PATCH 067/400] Create synthetic LifetimeEntry for a DHO when not supplied Now, a DHO is always associated with a HitObjectLifetimeEntry while used. Result is always stored in the entry, and not in the DHO. --- .../Objects/Drawables/DrawableHitObject.cs | 87 ++++++++----------- .../Objects/UnmanagedHitObjectEntry.cs | 20 +++++ 2 files changed, 55 insertions(+), 52 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index dfeb87de88..9b132ea932 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -39,7 +39,12 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The currently represented by this . /// - public HitObject HitObject { get; private set; } + public HitObject HitObject => lifetimeEntry?.HitObject ?? initialHitObject; + + /// + /// The given in the constructor that will be applied when loaded. + /// + private HitObject initialHitObject; /// /// The parenting , if any. @@ -108,7 +113,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The scoring result of this . /// - public JudgementResult Result { get; private set; } + public JudgementResult Result => lifetimeEntry?.Result; /// /// The relative X position of this hit object for sample playback balance adjustment. @@ -140,11 +145,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public IBindable State => state; - /// - /// Whether is currently applied. - /// - private bool hasHitObjectApplied; - /// /// The controlling the lifetime of the currently-attached . /// @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) { - HitObject = initialHitObject; + this.initialHitObject = initialHitObject; } [BackgroundDependencyLoader] @@ -184,8 +184,11 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadAsyncComplete(); - if (HitObject != null) - Apply(HitObject, lifetimeEntry); + if (initialHitObject != null) + { + Apply(initialHitObject, null); + initialHitObject = null; + } } protected override void LoadComplete() @@ -209,26 +212,36 @@ namespace osu.Game.Rulesets.Objects.Drawables if (lifetimeEntry != null) { - applyEntry(lifetimeEntry); + if (lifetimeEntry.HitObject != hitObject) + throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); + + apply(lifetimeEntry); } else { - applyHitObject(hitObject); + var unmanagedEntry = new UnmanagedHitObjectEntry(hitObject, this); + apply(unmanagedEntry); // Set default lifetime for a non-pooled DHO LifetimeStart = hitObject.StartTime - InitialLifetimeOffset; } } - private void applyHitObject([NotNull] HitObject hitObject) + /// + /// Applies a new to be represented by this . + /// + private void apply([NotNull] HitObjectLifetimeEntry entry) { - freeHitObject(); + free(); - HitObject = hitObject; + lifetimeEntry = entry; + + LifetimeStart = entry.LifetimeStart; + LifetimeEnd = entry.LifetimeEnd; // Ensure this DHO has a result. - Result ??= CreateResult(HitObject.CreateJudgement()) - ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + entry.Result ??= CreateResult(HitObject.CreateJudgement()) + ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); // Copy back the result to the entry for potential future retrieval. if (lifetimeEntry != null) @@ -281,30 +294,14 @@ namespace osu.Game.Rulesets.Objects.Drawables else updateState(ArmedState.Idle, true); } - - hasHitObjectApplied = true; - } - - private void applyEntry([NotNull] HitObjectLifetimeEntry entry) - { - freeEntry(); - - setLifetime(entry.LifetimeStart, entry.LifetimeEnd); - lifetimeEntry = entry; - - // Copy any existing result from the entry (required for rewind / judgement revert). - Result = entry.Result; - - applyHitObject(entry.HitObject); } /// - /// Removes the currently applied + /// Removes the currently applied /// - private void freeHitObject() + private void free() { - if (!hasHitObjectApplied) - return; + if (lifetimeEntry == null) return; StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); if (HitObject is IHasComboInformation combo) @@ -336,24 +333,10 @@ namespace osu.Game.Rulesets.Objects.Drawables OnFree(); - HitObject = null; ParentHitObject = null; - Result = null; - - clearExistingStateTransforms(); - - hasHitObjectApplied = false; - } - - private void freeEntry() - { - freeHitObject(); - - if (lifetimeEntry == null) return; - lifetimeEntry = null; - setLifetime(double.MaxValue, double.MaxValue); + clearExistingStateTransforms(); } protected sealed override void FreeAfterUse() @@ -364,7 +347,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (!IsInPool) return; - freeEntry(); + free(); } /// diff --git a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs new file mode 100644 index 0000000000..507cad15d3 --- /dev/null +++ b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.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.Objects.Drawables; + +namespace osu.Game.Rulesets.Objects +{ + internal class UnmanagedHitObjectEntry : HitObjectLifetimeEntry + { + public readonly DrawableHitObject DrawableHitObject; + + public UnmanagedHitObjectEntry(HitObject hitObject, DrawableHitObject drawableHitObject) + : base(hitObject) + { + DrawableHitObject = drawableHitObject; + LifetimeStart = DrawableHitObject.LifetimeStart; + LifetimeEnd = DrawableHitObject.LifetimeEnd; + } + } +} From 510e54ff5405a944344412fbd72b9c3418e4d28e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 23:29:14 +0900 Subject: [PATCH 068/400] Update framework --- osu.Android.props | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 0bb0bf171c..ed2b27e1c7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index bcbb0f4366..aee431284e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - playfield.Rotation = (Direction.Value == RotationDirection.CounterClockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); + playfield.Rotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e0a267241d..509cb3ddad 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index bcd953c0bd..4b67bd78a1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 0825fc57a9f50a86594b4090954366d47aa82725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Apr 2021 18:24:15 +0200 Subject: [PATCH 069/400] Move foreground colour helper into `OsuColour` --- osu.Game/Graphics/OsuColour.cs | 12 ++++++++++ .../Graphics/UserInterfaceV2/ColourDisplay.cs | 3 +-- .../Timeline/TimelineHitObjectBlueprint.cs | 3 +-- osu.Game/Utils/ColourUtils.cs | 23 ------------------- 4 files changed, 14 insertions(+), 27 deletions(-) delete mode 100644 osu.Game/Utils/ColourUtils.cs diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index c3b9b6006c..15967c37c2 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -94,6 +94,18 @@ namespace osu.Game.Graphics } } + /// + /// Returns a foreground text colour that is supposed to contrast well with + /// the supplied . + /// + public static Color4 ForegroundTextColourFor(Color4 backgroundColour) + { + // formula taken from the RGB->YIQ conversions: https://en.wikipedia.org/wiki/YIQ + // brightness here is equivalent to the Y component in the above colour model, which is a rough estimate of lightness. + float brightness = 0.299f * backgroundColour.R + 0.587f * backgroundColour.G + 0.114f * backgroundColour.B; + return Gray(brightness > 0.5f ? 0.2f : 0.9f); + } + // See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff"); public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff"); diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs index c07e5d5bf7..01d91f7cfd 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; -using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -102,7 +101,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { fill.Colour = current.Value; colourHexCode.Text = current.Value.ToHex(); - colourHexCode.Colour = ColourUtils.ForegroundTextColourFor(current.Value); + colourHexCode.Colour = OsuColour.ForegroundTextColourFor(current.Value); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index dc67009d08..0425370ae5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -21,7 +21,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; -using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -159,7 +158,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline circle.Colour = comboColour; var col = circle.Colour.TopLeft.Linear; - colouredComponents.Colour = ColourUtils.ForegroundTextColourFor(col); + colouredComponents.Colour = OsuColour.ForegroundTextColourFor(col); } protected override void Update() diff --git a/osu.Game/Utils/ColourUtils.cs b/osu.Game/Utils/ColourUtils.cs deleted file mode 100644 index 33f6f5981f..0000000000 --- a/osu.Game/Utils/ColourUtils.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 osu.Game.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Utils -{ - public static class ColourUtils - { - /// - /// Returns a foreground text colour that is supposed to contrast well on top of - /// the supplied . - /// - public static Color4 ForegroundTextColourFor(Color4 backgroundColour) - { - // formula taken from the RGB->YIQ conversions: https://en.wikipedia.org/wiki/YIQ - // brightness here is equivalent to the Y component in the above colour model, which is a rough estimate of lightness. - float brightness = 0.299f * backgroundColour.R + 0.587f * backgroundColour.G + 0.114f * backgroundColour.B; - return OsuColour.Gray(brightness > 0.5f ? 0.2f : 0.9f); - } - } -} From 8656176ab8e09b14737212846fdeed787a3f7bc4 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:31:51 +0200 Subject: [PATCH 070/400] Add the playable beatmap as check argument This is different from the working beatmap's `.Beatmap` property in that it is mutated by the ruleset/editor. So hit objects, for example, are actually of type `Slider` and such instead of the legacy `ConvertSlider`. This should be preferred over `workingBeatmap.Beatmap`. --- .../Editor/Checks/CheckOffscreenObjectsTest.cs | 6 +++--- .../Edit/Checks/CheckOffscreenObjects.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs | 5 ++++- .../Editing/Checks/CheckBackgroundQualityTest.cs | 12 ++++++------ .../Editing/Checks/CheckFilePresenceTest.cs | 12 ++++++------ osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 5 ++++- .../Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 6 +++--- osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs | 4 ++-- osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs | 5 +++-- osu.Game/Rulesets/Edit/IBeatmapVerifier.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 4 ++-- 11 files changed, 36 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index db347960ef..3a4817398c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -224,12 +224,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOk(IBeatmap beatmap) { - Assert.That(check.Run(new TestWorkingBeatmap(beatmap)), Is.Empty); + Assert.That(check.Run(beatmap, new TestWorkingBeatmap(beatmap)), Is.Empty); } private void assertOffscreenCircle(IBeatmap beatmap) { - var issues = check.Run(new TestWorkingBeatmap(beatmap)).ToList(); + var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle); @@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOffscreenSlider(IBeatmap beatmap) { - var issues = check.Run(new TestWorkingBeatmap(beatmap)).ToList(); + var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider); diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index c210f73b5f..4b0a7531a1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -31,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks new IssueTemplateOffscreenSlider(this) }; - public IEnumerable Run(IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { - foreach (var hitobject in workingBeatmap.Beatmap.HitObjects) + foreach (var hitobject in playableBeatmap.HitObjects) { switch (hitobject) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 9b9383d547..dab6483179 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -17,6 +17,9 @@ namespace osu.Game.Rulesets.Osu.Edit new CheckOffscreenObjects() }; - public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); + public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) + { + return checks.SelectMany(check => check.Run(playableBeatmap, workingBeatmap)); + } } } diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 8dad5bbf34..42d143cc04 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Editing.Checks beatmap.Metadata.BackgroundFile = null; var mock = getMockWorkingBeatmap(null, System.Array.Empty()); - Assert.That(check.Run(mock.Object), Is.Empty); + Assert.That(check.Run(beatmap, mock.Object), Is.Empty); } [Test] @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Editing.Checks { var mock = getMockWorkingBeatmap(new Texture(1920, 1080)); - Assert.That(check.Run(mock.Object), Is.Empty); + Assert.That(check.Run(beatmap, mock.Object), Is.Empty); } [Test] @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Editing.Checks { var mock = getMockWorkingBeatmap(new Texture(3840, 2160)); - var issues = check.Run(mock.Object).ToList(); + var issues = check.Run(beatmap, mock.Object).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooHighResolution); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Editing.Checks { var mock = getMockWorkingBeatmap(new Texture(640, 480)); - var issues = check.Run(mock.Object).ToList(); + var issues = check.Run(beatmap, mock.Object).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateLowResolution); @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Editing.Checks { var mock = getMockWorkingBeatmap(new Texture(100, 100)); - var issues = check.Run(mock.Object).ToList(); + var issues = check.Run(beatmap, mock.Object).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooLowResolution); @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Editing.Checks { var mock = getMockWorkingBeatmap(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); - var issues = check.Run(mock.Object).ToList(); + var issues = check.Run(beatmap, mock.Object).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed); diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index 1149115b55..fe2e16ffd8 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Tests.Editing.Checks public class CheckFilePresenceTest { private CheckBackgroundPresence check; - private WorkingBeatmap beatmap; + private IBeatmap beatmap; [SetUp] public void Setup() { check = new CheckBackgroundPresence(); - beatmap = new TestWorkingBeatmap(new Beatmap + beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { @@ -34,13 +34,13 @@ namespace osu.Game.Tests.Editing.Checks }) } } - }); + }; } [Test] public void TestBackgroundSetAndInFiles() { - Assert.That(check.Run(beatmap), Is.Empty); + Assert.That(check.Run(beatmap, new TestWorkingBeatmap(beatmap)), Is.Empty); } [Test] @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Editing.Checks { beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); - var issues = check.Run(beatmap).ToList(); + var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateDoesNotExist); @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Editing.Checks { beatmap.Metadata.BackgroundFile = null; - var issues = check.Run(beatmap).ToList(); + var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateNoneSet); diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 90447049f8..9efaa94b40 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -23,6 +23,9 @@ namespace osu.Game.Rulesets.Edit new CheckAudioPresence(), }; - public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); + public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) + { + return checks.SelectMany(check => check.Run(playableBeatmap, workingBeatmap)); + } } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 1494ae5da4..767c2b94b5 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -30,9 +30,9 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateTooUncompressed(this) }; - public IEnumerable Run(IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { - var backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; + var backgroundFile = playableBeatmap.Metadata?.BackgroundFile; if (backgroundFile == null) yield break; @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Edit.Checks else if (texture.Width < low_width || texture.Height < low_height) yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); - string storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); + string storagePath = playableBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); double filesizeMb = workingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); if (filesizeMb > max_filesize_mb) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index 37fa79568f..340b053fdb 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateDoesNotExist(this) }; - public IEnumerable Run(IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { var filename = GetFilename(workingBeatmap); @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Edit.Checks } // If the file is set, also make sure it still exists. - var storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); + var storagePath = playableBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); if (storagePath != null) yield break; diff --git a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs index c3a64b58e9..31a7583941 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs @@ -24,7 +24,8 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// Runs this check and returns any issues detected for the provided beatmap. /// - /// The beatmap to run the check on. - public IEnumerable Run(IWorkingBeatmap workingBeatmap); + /// The playable beatmap of the beatmap to run the check on. + /// The working beatmap of the beatmap to run the check on. + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap); } } diff --git a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs index 12be8815e1..b598176a35 100644 --- a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs @@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Edit /// public interface IBeatmapVerifier { - public IEnumerable Run(WorkingBeatmap workingBeatmap); + public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap); } } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 6aa479b38d..393c89fe52 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -124,10 +124,10 @@ namespace osu.Game.Screens.Edit.Verify private void refresh() { var workingBeatmap = beatmapManager.GetWorkingBeatmap(beatmap.BeatmapInfo); - var issues = generalVerifier.Run(workingBeatmap); + var issues = generalVerifier.Run(beatmap.PlayableBeatmap, workingBeatmap); if (rulesetVerifier != null) - issues = issues.Concat(rulesetVerifier.Run(workingBeatmap)); + issues = issues.Concat(rulesetVerifier.Run(beatmap.PlayableBeatmap, workingBeatmap)); table.Issues = issues .OrderBy(issue => issue.Template.Type) From be6a02a17e3f5621d7f6942678d278748cd88cec Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:32:22 +0200 Subject: [PATCH 071/400] Simplify background quality test names --- .../Editing/Checks/CheckBackgroundQualityTest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 42d143cc04..b6970a99e4 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundMissing() + public void TestMissing() { // While this is a problem, it is out of scope for this check and is caught by a different one. beatmap.Metadata.BackgroundFile = null; @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundAcceptable() + public void TestAcceptable() { var mock = getMockWorkingBeatmap(new Texture(1920, 1080)); @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundTooHighResolution() + public void TestTooHighResolution() { var mock = getMockWorkingBeatmap(new Texture(3840, 2160)); @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundLowResolution() + public void TestLowResolution() { var mock = getMockWorkingBeatmap(new Texture(640, 480)); @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundTooLowResolution() + public void TestTooLowResolution() { var mock = getMockWorkingBeatmap(new Texture(100, 100)); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundTooUncompressed() + public void TestTooUncompressed() { var mock = getMockWorkingBeatmap(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); From 14c626ffcbff01f250a7008ebb8469181f5d6f95 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:33:19 +0200 Subject: [PATCH 072/400] Use the playable beatmap for file presence checks --- osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs index 55e53ef519..2d572a521e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Edit.Checks { protected override CheckCategory Category => CheckCategory.Audio; protected override string TypeOfFile => "audio"; - protected override string GetFilename(IWorkingBeatmap workingBeatmap) => workingBeatmap.Beatmap.Metadata?.AudioFile; + protected override string GetFilename(IBeatmap playableBeatmap) => playableBeatmap.Metadata?.AudioFile; } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs index 3a229b889b..233c708a25 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Edit.Checks { protected override CheckCategory Category => CheckCategory.Resources; protected override string TypeOfFile => "background"; - protected override string GetFilename(IWorkingBeatmap workingBeatmap) => workingBeatmap.Beatmap.Metadata?.BackgroundFile; + protected override string GetFilename(IBeatmap playableBeatmap) => playableBeatmap.Metadata?.BackgroundFile; } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index 340b053fdb..006fc57c04 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Edit.Checks { protected abstract CheckCategory Category { get; } protected abstract string TypeOfFile { get; } - protected abstract string GetFilename(IWorkingBeatmap workingBeatmap); + protected abstract string GetFilename(IBeatmap playableBeatmap); public CheckMetadata Metadata => new CheckMetadata(Category, $"Missing {TypeOfFile}"); @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { - var filename = GetFilename(workingBeatmap); + var filename = GetFilename(playableBeatmap); if (filename == null) { From 40ae856dfc4cb5006f43f10e3b1363e8434cb5ea Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:34:05 +0200 Subject: [PATCH 073/400] Show 2 decimals for background filesize --- osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 767c2b94b5..59fee74023 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateTooUncompressed : IssueTemplate { public IssueTemplateTooUncompressed(ICheck check) - : base(check, IssueType.Problem, "The background filesize ({0:0.#} MB) exceeds {1} MB.") + : base(check, IssueType.Problem, "The background filesize ({0:0.##} MB) exceeds {1} MB.") { } From f168247254424bda53e90680bfc45e83b69e7f22 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:35:41 +0200 Subject: [PATCH 074/400] Add `Track` as a property to `IWorkingBeatmap` This is implemented by `WorkingBeatmap` already, and is much better to use than loading the track every time we need it. --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index f1bf6f48ef..b1cf6db6fc 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -42,6 +42,11 @@ namespace osu.Game.Beatmaps /// ISkin Skin { get; } + /// + /// Get the loaded audio track instance. + /// + Track Track { get; } + /// /// Constructs a playable from using the applicable converters for a specific . /// From c633f155653ef109898471b489e2ef466f1a10fc Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:36:03 +0200 Subject: [PATCH 075/400] Add audio quality check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 2 + .../Rulesets/Edit/Checks/CheckAudioQuality.cs | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 9efaa94b40..f33feac971 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -19,8 +19,10 @@ namespace osu.Game.Rulesets.Edit // Resources new CheckBackgroundPresence(), new CheckBackgroundQuality(), + // Audio new CheckAudioPresence(), + new CheckAudioQuality() }; public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs new file mode 100644 index 0000000000..b67374c023 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.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.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckAudioQuality : ICheck + { + // This is a requirement as stated in the Ranking Criteria. + // See https://osu.ppy.sh/wiki/en/Ranking_Criteria#rules.4 + private const int max_bitrate = 192; + + // "A song's audio file /.../ must be of reasonable quality. Try to find the highest quality source file available" + // There not existing a version with a bitrate of 128 kbps or higher is extremely rare. + private const int min_bitrate = 128; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Too high or low audio bitrate"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateTooHighBitrate(this), + new IssueTemplateTooLowBitrate(this), + new IssueTemplateNoBitrate(this) + }; + + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + { + var audioFile = playableBeatmap.Metadata?.AudioFile; + if (audioFile == null) + yield break; + + var track = workingBeatmap.Track; + + if (track?.Bitrate == null || track.Bitrate.Value == 0) + yield return new IssueTemplateNoBitrate(this).Create(); + else if (track.Bitrate.Value > max_bitrate) + yield return new IssueTemplateTooHighBitrate(this).Create(track.Bitrate.Value); + else if (track.Bitrate.Value < min_bitrate) + yield return new IssueTemplateTooLowBitrate(this).Create(track.Bitrate.Value); + } + + public class IssueTemplateTooHighBitrate : IssueTemplate + { + public IssueTemplateTooHighBitrate(ICheck check) + : base(check, IssueType.Problem, "The audio bitrate ({0} kbps) exceeds {1} kbps.") + { + } + + public Issue Create(int bitrate) => new Issue(this, bitrate, max_bitrate); + } + + public class IssueTemplateTooLowBitrate : IssueTemplate + { + public IssueTemplateTooLowBitrate(ICheck check) + : base(check, IssueType.Problem, "The audio bitrate ({0} kbps) is lower than {1} kbps.") + { + } + + public Issue Create(int bitrate) => new Issue(this, bitrate, min_bitrate); + } + + public class IssueTemplateNoBitrate : IssueTemplate + { + public IssueTemplateNoBitrate(ICheck check) + : base(check, IssueType.Error, "The audio bitrate could not be retrieved.") + { + } + + public Issue Create() => new Issue(this); + } + } +} From 2bb079ea1403479e1bf513eb63fdc3f1d98bceb3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:36:15 +0200 Subject: [PATCH 076/400] Add audio quality check tests --- .../Editing/Checks/CheckAudioQualityTest.cs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs new file mode 100644 index 0000000000..e73d9adfb0 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs @@ -0,0 +1,114 @@ +// 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 Moq; +using NUnit.Framework; +using osu.Framework.Audio.Track; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckAudioQualityTest + { + private CheckAudioQuality check; + private IBeatmap beatmap; + + [SetUp] + public void Setup() + { + check = new CheckAudioQuality(); + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" } + } + }; + } + + [Test] + public void TestMissing() + { + // While this is a problem, it is out of scope for this check and is caught by a different one. + beatmap.Metadata.AudioFile = null; + + var mock = new Mock(); + mock.SetupGet(_ => _.Beatmap).Returns(beatmap); + mock.SetupGet(_ => _.Track).Returns((Track)null); + + Assert.That(check.Run(beatmap, mock.Object), Is.Empty); + } + + [Test] + public void TestAcceptable() + { + var mock = getMockWorkingBeatmap(192); + + Assert.That(check.Run(beatmap, mock.Object), Is.Empty); + } + + [Test] + public void TestNullBitrate() + { + var mock = getMockWorkingBeatmap(null); + + var issues = check.Run(beatmap, mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate); + } + + [Test] + public void TestZeroBitrate() + { + var mock = getMockWorkingBeatmap(0); + + var issues = check.Run(beatmap, mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate); + } + + [Test] + public void TestTooHighBitrate() + { + var mock = getMockWorkingBeatmap(320); + + var issues = check.Run(beatmap, mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooHighBitrate); + } + + [Test] + public void TestTooLowBitrate() + { + var mock = getMockWorkingBeatmap(64); + + var issues = check.Run(beatmap, mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooLowBitrate); + } + + /// + /// Returns the mock of the working beatmap with the given audio properties. + /// + /// The bitrate of the audio file the beatmap uses. + private Mock getMockWorkingBeatmap(int? audioBitrate) + { + var mockTrack = new Mock(); + mockTrack.SetupGet(_ => _.Bitrate).Returns(audioBitrate); + + var mockWorkingBeatmap = new Mock(); + mockWorkingBeatmap.SetupGet(_ => _.Beatmap).Returns(beatmap); + mockWorkingBeatmap.SetupGet(_ => _.Track).Returns(mockTrack.Object); + + return mockWorkingBeatmap; + } + } +} From c1b4aaaa03334995f4a96f223b3ce446a8ffb0dc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 08:38:02 +0900 Subject: [PATCH 077/400] Add doc comment --- osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs index 507cad15d3..86fa59f589 100644 --- a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs +++ b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs @@ -5,6 +5,10 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Objects { + /// + /// Created for a when only is given + /// to make sure a is always associated with a . + /// internal class UnmanagedHitObjectEntry : HitObjectLifetimeEntry { public readonly DrawableHitObject DrawableHitObject; From 4510e795e1326257168b1174a551ecf281a45563 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 02:13:26 +0200 Subject: [PATCH 078/400] Fix category of audio quality check --- osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs index b67374c023..c1074d7c74 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Edit.Checks // There not existing a version with a bitrate of 128 kbps or higher is extremely rare. private const int min_bitrate = 128; - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Too high or low audio bitrate"); + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Too high or low audio bitrate"); public IEnumerable PossibleTemplates => new IssueTemplate[] { From 1bc63a4c611a99c9652743a9ba94eb6acc288e0a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 09:17:13 +0900 Subject: [PATCH 079/400] Now, DHO.lifetimeEntry can be non-null even it is not fully applied --- .../Objects/Drawables/DrawableHitObject.cs | 46 ++++++++----------- .../Objects/UnmanagedHitObjectEntry.cs | 7 +-- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9b132ea932..16f5ed9d17 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -39,12 +39,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The currently represented by this . /// - public HitObject HitObject => lifetimeEntry?.HitObject ?? initialHitObject; - - /// - /// The given in the constructor that will be applied when loaded. - /// - private HitObject initialHitObject; + public HitObject HitObject => lifetimeEntry?.HitObject; /// /// The parenting , if any. @@ -145,9 +140,15 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public IBindable State => state; + /// + /// Whether a is currently applied. + /// + private bool hasEntryApplied; + /// /// The controlling the lifetime of the currently-attached . /// + /// Even if it is not null, it may not be fully applied until loaded ( is false). [CanBeNull] private HitObjectLifetimeEntry lifetimeEntry; @@ -168,7 +169,8 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) { - this.initialHitObject = initialHitObject; + if (initialHitObject != null) + lifetimeEntry = new UnmanagedHitObjectEntry(initialHitObject); } [BackgroundDependencyLoader] @@ -184,11 +186,8 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadAsyncComplete(); - if (initialHitObject != null) - { - Apply(initialHitObject, null); - initialHitObject = null; - } + if (lifetimeEntry != null && !hasEntryApplied) + apply(lifetimeEntry); } protected override void LoadComplete() @@ -210,21 +209,10 @@ namespace osu.Game.Rulesets.Objects.Drawables if (hitObject == null) throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); - if (lifetimeEntry != null) - { - if (lifetimeEntry.HitObject != hitObject) - throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); + if (lifetimeEntry != null && lifetimeEntry.HitObject != hitObject) + throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); - apply(lifetimeEntry); - } - else - { - var unmanagedEntry = new UnmanagedHitObjectEntry(hitObject, this); - apply(unmanagedEntry); - - // Set default lifetime for a non-pooled DHO - LifetimeStart = hitObject.StartTime - InitialLifetimeOffset; - } + apply(lifetimeEntry ?? new UnmanagedHitObjectEntry(hitObject)); } /// @@ -294,6 +282,8 @@ namespace osu.Game.Rulesets.Objects.Drawables else updateState(ArmedState.Idle, true); } + + hasEntryApplied = true; } /// @@ -301,7 +291,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// private void free() { - if (lifetimeEntry == null) return; + if (!hasEntryApplied) return; StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); if (HitObject is IHasComboInformation combo) @@ -337,6 +327,8 @@ namespace osu.Game.Rulesets.Objects.Drawables lifetimeEntry = null; clearExistingStateTransforms(); + + hasEntryApplied = false; } protected sealed override void FreeAfterUse() diff --git a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs index 86fa59f589..3cfa3c4d3a 100644 --- a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs +++ b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs @@ -11,14 +11,9 @@ namespace osu.Game.Rulesets.Objects /// internal class UnmanagedHitObjectEntry : HitObjectLifetimeEntry { - public readonly DrawableHitObject DrawableHitObject; - - public UnmanagedHitObjectEntry(HitObject hitObject, DrawableHitObject drawableHitObject) + public UnmanagedHitObjectEntry(HitObject hitObject) : base(hitObject) { - DrawableHitObject = drawableHitObject; - LifetimeStart = DrawableHitObject.LifetimeStart; - LifetimeEnd = DrawableHitObject.LifetimeEnd; } } } From 67e4fe42847fab7f6cc47b422257971b13979e06 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 02:28:38 +0200 Subject: [PATCH 080/400] Add xmldoc to `GetStream` --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index b1cf6db6fc..053c6d373f 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -74,6 +74,10 @@ namespace osu.Game.Beatmaps /// A fresh track instance, which will also be available via . Track LoadTrack(); + /// + /// Returns the stream of the file from the given storage path. + /// + /// The storage path to the file. Stream GetStream(string storagePath); } } From 1478bcfa8e774dcbb3e1b785b94bae10c2d8acbd Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 02:30:27 +0200 Subject: [PATCH 081/400] Improve xmldoc consistency --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 053c6d373f..a916b37b85 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps ISkin Skin { get; } /// - /// Get the loaded audio track instance. + /// Retrieves the which this has loaded. /// Track Track { get; } From 496df411a7e20485adf374233f5f4d93a292d6b7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 02:39:11 +0200 Subject: [PATCH 082/400] Remove now unused import --- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 2ab529cb27..530f24300b 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; From 8a8b9084efa04747e8147d7602fc277359c18b43 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 10:11:36 +0900 Subject: [PATCH 083/400] Make single-argument overloead of DHO.Apply public --- .../Objects/Drawables/DrawableHitObject.cs | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 16f5ed9d17..59088cffda 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -165,7 +166,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// /// The to be initially applied to this . - /// If null, a hitobject is expected to be later applied via (or automatically via pooling). + /// If null, a hitobject is expected to be later applied via (or automatically via pooling). /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) { @@ -187,7 +188,7 @@ namespace osu.Game.Rulesets.Objects.Drawables base.LoadAsyncComplete(); if (lifetimeEntry != null && !hasEntryApplied) - apply(lifetimeEntry); + Apply(lifetimeEntry); } protected override void LoadComplete() @@ -200,36 +201,47 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Applies a new to be represented by this . + /// Applies a hit object to be represented by this . /// - /// The to apply. - /// The controlling the lifetime of . + /// This overload is semi-deprecated. Use either or . public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry) + { + if (lifetimeEntry != null && lifetimeEntry.HitObject != hitObject) + throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); + + if (lifetimeEntry != null) + Apply(lifetimeEntry); + else + Apply(hitObject); + } + + /// + /// Applies a new to be represented by this . + /// A new is automatically created and applied to this . + /// + public void Apply([NotNull] HitObject hitObject) { if (hitObject == null) throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); - if (lifetimeEntry != null && lifetimeEntry.HitObject != hitObject) - throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); - - apply(lifetimeEntry ?? new UnmanagedHitObjectEntry(hitObject)); + Apply(new UnmanagedHitObjectEntry(hitObject)); } /// /// Applies a new to be represented by this . /// - private void apply([NotNull] HitObjectLifetimeEntry entry) + public void Apply([NotNull] HitObjectLifetimeEntry newEntry) { free(); - lifetimeEntry = entry; + lifetimeEntry = newEntry; - LifetimeStart = entry.LifetimeStart; - LifetimeEnd = entry.LifetimeEnd; + LifetimeStart = lifetimeEntry.LifetimeStart; + LifetimeEnd = lifetimeEntry.LifetimeEnd; // Ensure this DHO has a result. - entry.Result ??= CreateResult(HitObject.CreateJudgement()) - ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + lifetimeEntry.Result ??= CreateResult(HitObject.CreateJudgement()) + ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); // Copy back the result to the entry for potential future retrieval. if (lifetimeEntry != null) @@ -387,7 +399,9 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onDefaultsApplied(HitObject hitObject) { - Apply(hitObject, lifetimeEntry); + Debug.Assert(lifetimeEntry != null); + Apply(lifetimeEntry); + DefaultsApplied?.Invoke(this); } From a92ae8ce769aea4ff4d2c293ce3e825970985ba3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 13:01:42 +0900 Subject: [PATCH 084/400] Fix Reset() potentially not resetting to the intended start position --- osu.Game/Screens/Play/GameplayClockContainer.cs | 10 +++++++++- .../Screens/Play/MasterGameplayClockContainer.cs | 15 +++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 3da28f3560..e84f34e0d5 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -29,6 +29,11 @@ namespace osu.Game.Screens.Play /// protected readonly DecoupleableInterpolatingFramedClock AdjustableSource; + /// + /// The offset at which to start playing. Affects the time which the clock is reset to via . + /// + protected virtual double StartOffset => 0; + /// /// The source clock. /// @@ -93,9 +98,12 @@ namespace osu.Game.Screens.Play { ChangeSource(SourceClock); - AdjustableSource.Seek(0); + AdjustableSource.Seek(StartOffset); AdjustableSource.Stop(); + // Make sure the gameplay clock takes on the new time, otherwise the adjustable source will be seeked to the gameplay clock time in Start(). + GameplayClock.UnderlyingClock.ProcessFrame(); + if (!IsPaused.Value) Start(); } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index f019e50b60..05d34fb8d4 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -47,6 +47,9 @@ namespace osu.Game.Screens.Play private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); + protected override double StartOffset => startOffset; + private double startOffset; + private readonly WorkingBeatmap beatmap; private readonly double gameplayStartTime; private readonly bool startAtGameplayStart; @@ -74,27 +77,23 @@ namespace osu.Game.Screens.Play userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); // sane default provided by ruleset. - double startTime = gameplayStartTime; + startOffset = gameplayStartTime; if (!startAtGameplayStart) { - startTime = Math.Min(0, startTime); + startOffset = Math.Min(0, startOffset); // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. // this is commonly used to display an intro before the audio track start. double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime; if (firstStoryboardEvent != null) - startTime = Math.Min(startTime, firstStoryboardEvent.Value); + startOffset = Math.Min(startOffset, firstStoryboardEvent.Value); // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. // this is not available as an option in the live editor but can still be applied via .osu editing. if (beatmap.BeatmapInfo.AudioLeadIn > 0) - startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + startOffset = Math.Min(startOffset, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); } - - Seek(startTime); - - AdjustableSource.ProcessFrame(); } protected override void OnIsPausedChanged(ValueChangedEvent isPaused) From 8dd9134e3d9b6304c3a3ad8d4f869ea73394d42e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 13:09:49 +0900 Subject: [PATCH 085/400] Move source clock adjustment application to Start() --- .../Screens/Play/MasterGameplayClockContainer.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 05d34fb8d4..f1d303e245 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -105,6 +105,12 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } + public override void Start() + { + addSourceClockAdjustments(); + base.Start(); + } + /// /// Seek to a specific time in gameplay. /// @@ -122,12 +128,6 @@ namespace osu.Game.Screens.Play userOffsetClock.ProcessFrame(); } - public override void Reset() - { - updateRate(); - base.Reset(); - } - /// /// Skip forward to the next valid skip point. /// @@ -164,11 +164,12 @@ namespace osu.Game.Screens.Play { removeSourceClockAdjustments(); ChangeSource(new TrackVirtual(beatmap.Track.Length)); + addSourceClockAdjustments(); } private bool speedAdjustmentsApplied; - private void updateRate() + private void addSourceClockAdjustments() { if (speedAdjustmentsApplied) return; From 88ded95e7587c6345da8ecd186a46aba26600524 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 13:56:13 +0900 Subject: [PATCH 086/400] Ensure clock is set in GCC.Start() --- .../TestSceneMasterGameplayClockContainer.cs | 28 ++++++++++++++++++- .../Screens/Play/GameplayClockContainer.cs | 7 +++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 77ada958d7..935bc07733 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -25,8 +25,34 @@ namespace osu.Game.Tests.Gameplay Add(gcc = new MasterGameplayClockContainer(working, 0)); }); - AddStep("start track", () => gcc.Start()); + AddStep("start clock", () => gcc.Start()); AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0); } + + [Test] + public void TestElapseThenReset() + { + GameplayClockContainer gcc = null; + + AddStep("create container", () => + { + var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + working.LoadTrack(); + + Add(gcc = new MasterGameplayClockContainer(working, 0)); + }); + + AddStep("start clock", () => gcc.Start()); + AddUntilStep("current time greater 2000", () => gcc.GameplayClock.CurrentTime > 2000); + + double timeAtReset = 0; + AddStep("reset clock", () => + { + timeAtReset = gcc.GameplayClock.CurrentTime; + gcc.Reset(); + }); + + AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset); + } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index e84f34e0d5..75b27ed3f3 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.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.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Timing; namespace osu.Game.Screens.Play @@ -68,6 +70,9 @@ namespace osu.Game.Screens.Play /// public virtual void Start() { + // Ensure that the source clock is set. + ChangeSource(SourceClock); + if (!AdjustableSource.IsRunning) { // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time @@ -96,8 +101,6 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { - ChangeSource(SourceClock); - AdjustableSource.Seek(StartOffset); AdjustableSource.Stop(); From 3d6d26039a0a1040964f532b57ac912fef9716c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 14:09:54 +0900 Subject: [PATCH 087/400] Remove unused usings --- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 75b27ed3f3..0ada6613d7 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -1,12 +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.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; using osu.Framework.Timing; namespace osu.Game.Screens.Play From c6ee4e900e25201389ba194b768832e45322941f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 15:18:36 +0900 Subject: [PATCH 088/400] Ensure a non-null hitobject entry has a non-null Result --- .../Objects/Drawables/DrawableHitObject.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 59088cffda..d798eba4e1 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -171,7 +171,10 @@ namespace osu.Game.Rulesets.Objects.Drawables protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) { if (initialHitObject != null) + { lifetimeEntry = new UnmanagedHitObjectEntry(initialHitObject); + ensureEntryHasResult(); + } } [BackgroundDependencyLoader] @@ -239,13 +242,7 @@ namespace osu.Game.Rulesets.Objects.Drawables LifetimeStart = lifetimeEntry.LifetimeStart; LifetimeEnd = lifetimeEntry.LifetimeEnd; - // Ensure this DHO has a result. - lifetimeEntry.Result ??= CreateResult(HitObject.CreateJudgement()) - ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); - - // Copy back the result to the entry for potential future retrieval. - if (lifetimeEntry != null) - lifetimeEntry.Result = Result; + ensureEntryHasResult(); foreach (var h in HitObject.NestedHitObjects) { @@ -799,6 +796,13 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The that provides the scoring information. protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement); + private void ensureEntryHasResult() + { + Debug.Assert(lifetimeEntry != null); + lifetimeEntry.Result ??= CreateResult(HitObject.CreateJudgement()) + ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 281c2041b23bdbce8859f9027220eb4b06fb908d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 16:51:00 +0900 Subject: [PATCH 089/400] Add failing test --- .../Gameplay/TestSceneStoryboardSamples.cs | 28 ++++++++++++++++++- osu.Game/Skinning/PausableSkinnableSound.cs | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index cae5f20332..f2cb5f75d8 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -77,7 +77,33 @@ namespace osu.Game.Tests.Gameplay AddStep("start time", () => gameplayContainer.Start()); - AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue); + AddUntilStep("sample played", () => sample.RequestedPlaying); + AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue); + } + + [Test] + public void TestSampleHasLifetimeEndWithInitialClockTime() + { + GameplayClockContainer gameplayContainer = null; + DrawableStoryboardSample sample = null; + + AddStep("create container", () => + { + var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + working.LoadTrack(); + + Add(gameplayContainer = new GameplayClockContainer(working, 1000, true)); + + gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + { + Clock = gameplayContainer.GameplayClock + }); + }); + + AddStep("start time", () => gameplayContainer.Start()); + + AddUntilStep("sample not played", () => !sample.RequestedPlaying); + AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue); } [TestCase(typeof(OsuModDoubleTime), 1.5)] diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index b3c7c5d6b2..10b8c47028 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -16,7 +16,7 @@ namespace osu.Game.Skinning { public double Length => !DrawableSamples.Any() ? 0 : DrawableSamples.Max(sample => sample.Length); - protected bool RequestedPlaying { get; private set; } + public bool RequestedPlaying { get; private set; } public PausableSkinnableSound() { From d28eb399a4539c776464e99fa322a33ebf1fa654 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 16:51:24 +0900 Subject: [PATCH 090/400] Fix storyboard sample lifetimes not set if seeked past --- .../Drawables/DrawableStoryboardSample.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 7b16009859..fbdd27e762 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -61,28 +61,32 @@ namespace osu.Game.Storyboards.Drawables { base.Update(); + // Check if we've yet to pass the sample start time. if (Time.Current < sampleInfo.StartTime) { - // We've rewound before the start time of the sample 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) + // Playback has stopped, but if the user fast-forwards to a point after the start time of the sample then + // we must not have a lifetime end in order to continue receiving updates and start the sample below. LifetimeStart = sampleInfo.StartTime; LifetimeEnd = double.MaxValue; + + return; } - else if (Time.Current - Time.Elapsed <= sampleInfo.StartTime) + + // Ensure that we've elapsed from a point before the sample's start time before playing. + 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 if (!RequestedPlaying && Time.Current - sampleInfo.StartTime < allowable_late_start) 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) - LifetimeStart = double.MinValue; - LifetimeEnd = sampleInfo.StartTime; } + + // Playback has started, but if the user rewinds to a point before the start time of the sample then + // we must not have a lifetime start in order to continue receiving updates and stop the sample above. + LifetimeStart = double.MinValue; + LifetimeEnd = sampleInfo.StartTime; } } } From 5da18c51a49bb3669dae12f18b9af89668bf873f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 17:27:37 +0900 Subject: [PATCH 091/400] Fix compile error --- 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 57b3687d3a..5b8abddd3f 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayContainer = new GameplayClockContainer(working, 1000, true)); + Add(gameplayContainer = new MasterGameplayClockContainer(working, 1000, true)); gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) { From 97fb90d9f45c660e4420fbf05477846e010eea35 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 17:35:59 +0900 Subject: [PATCH 092/400] Move clock processing to base.Seek() --- osu.Game/Screens/Play/GameplayClockContainer.cs | 10 +++++++--- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 3 --- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0ada6613d7..f6b99c094f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -87,7 +87,13 @@ namespace osu.Game.Screens.Play /// Seek to a specific time in gameplay. /// /// The destination time to seek to. - public virtual void Seek(double time) => AdjustableSource.Seek(time); + public virtual void Seek(double time) + { + AdjustableSource.Seek(time); + + // Manually process to make sure the gameplay clock is correctly updated after a seek. + GameplayClock.UnderlyingClock.ProcessFrame(); + } /// /// Stops gameplay. @@ -102,8 +108,6 @@ namespace osu.Game.Screens.Play AdjustableSource.Seek(StartOffset); AdjustableSource.Stop(); - // Make sure the gameplay clock takes on the new time, otherwise the adjustable source will be seeked to the gameplay clock time in Start(). - GameplayClock.UnderlyingClock.ProcessFrame(); if (!IsPaused.Value) Start(); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index f1d303e245..1b893a76c5 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -123,9 +123,6 @@ namespace osu.Game.Screens.Play // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. // we may want to consider reversing the application of offsets in the future as it may feel more correct. base.Seek(time - totalOffset); - - // manually process frame to ensure GameplayClock is correctly updated after a seek. - userOffsetClock.ProcessFrame(); } /// From 505a117862cce9818970a71833736c7a294b84c2 Mon Sep 17 00:00:00 2001 From: Denrage Date: Mon, 19 Apr 2021 23:41:51 +0200 Subject: [PATCH 093/400] splitted updateable part of wedge --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 16 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 261 ++++++++++-------- 2 files changed, 152 insertions(+), 125 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 7ea6373763..9c10c9751c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -112,8 +112,8 @@ namespace osu.Game.Tests.Visual.SongSelect private void testInfoLabels(int expectedCount) { - AddAssert("check info labels exists", () => infoWedge.Info.ChildrenOfType().Any()); - AddAssert("check info labels count", () => infoWedge.Info.ChildrenOfType().Count() == expectedCount); + AddAssert("check info labels exists", () => infoWedge.Info.ChildrenOfType().Any()); + AddAssert("check info labels count", () => infoWedge.Info.ChildrenOfType().Count() == expectedCount); } [Test] @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("check default title", () => infoWedge.Info.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title); AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist); AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); - AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType().Any()); + AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType().Any()); } [Test] @@ -135,15 +135,15 @@ namespace osu.Game.Tests.Visual.SongSelect private void selectBeatmap([CanBeNull] IBeatmap b) { - BeatmapInfoWedge.BufferedWedgeInfo infoBefore = null; + BeatmapInfoWedge.BufferedWedgeBackground backgroundBefore = null; AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () => { - infoBefore = infoWedge.Info; + backgroundBefore = infoWedge.Background; infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b); }); - AddUntilStep("wait for async load", () => infoWedge.Info != infoBefore); + AddUntilStep("wait for async load", () => infoWedge.Background != backgroundBefore); } private IBeatmap createTestBeatmap(RulesetInfo ruleset) @@ -193,7 +193,9 @@ namespace osu.Game.Tests.Visual.SongSelect private class TestBeatmapInfoWedge : BeatmapInfoWedge { - public new BufferedWedgeInfo Info => base.Info; + public new BufferedWedgeBackground Background => base.Background; + + public new WedgeInfoText Info => base.Info; } private class TestHitObject : ConvertHitObject, IHasPosition diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 4069dc82ed..0b72970bb0 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -49,7 +49,8 @@ namespace osu.Game.Screens.Select private IBindable beatmapDifficulty; - protected BufferedWedgeInfo Info; + protected BufferedWedgeBackground Background; + protected WedgeInfoText Info; public BeatmapInfoWedge() { @@ -110,9 +111,9 @@ namespace osu.Game.Screens.Select } } - public override bool IsPresent => base.IsPresent || Info == null; // Visibility is updated in the LoadComponentAsync callback + public override bool IsPresent => base.IsPresent || Background == null; // Visibility is updated in the LoadComponentAsync callback - private BufferedWedgeInfo loadingInfo; + private BufferedWedgeBackground loadingInfo; private void updateDisplay() { @@ -127,6 +128,10 @@ namespace osu.Game.Screens.Select Info?.FadeOut(250); Info?.Expire(); Info = null; + + Background?.FadeOut(250); + Background?.Expire(); + Background = null; } if (beatmap == null) @@ -135,17 +140,21 @@ namespace osu.Game.Screens.Select return; } - LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty()) + LoadComponentAsync(loadingInfo = new BufferedWedgeBackground(beatmap) { Shear = -Shear, - Depth = Info?.Depth + 1 ?? 0 + Depth = Background?.Depth + 1 ?? 0 }, loaded => { // ensure we are the most recent loaded wedge. if (loaded != loadingInfo) return; removeOldInfo(); - Add(Info = loaded); + Add(Background = loaded); + Add(Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty()) + { + Shear = -Shear + }); }); } } @@ -156,28 +165,26 @@ namespace osu.Game.Screens.Select cancellationSource?.Cancel(); } - public class BufferedWedgeInfo : BufferedContainer + public class WedgeInfoText : Container { - public OsuSpriteText VersionLabel { get; private set; } + public FillFlowContainer MapperContainer { get; private set; } public OsuSpriteText TitleLabel { get; private set; } public OsuSpriteText ArtistLabel { get; private set; } + public OsuSpriteText VersionLabel { get; private set; } public BeatmapSetOnlineStatusPill StatusPill { get; private set; } - public FillFlowContainer MapperContainer { get; private set; } private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; private FillFlowContainer infoLabelContainer; private Container bpmLabelContainer; + private ModSettingChangeTracker settingChangeTracker; private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; private readonly IReadOnlyList mods; private readonly StarDifficulty starDifficulty; - private ModSettingChangeTracker settingChangeTracker; - - public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList mods, StarDifficulty difficulty) - : base(pixelSnapping: true) + public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList mods, StarDifficulty difficulty) { this.beatmap = beatmap; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; @@ -191,7 +198,6 @@ namespace osu.Game.Screens.Select var beatmapInfo = beatmap.BeatmapInfo; var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - CacheDrawnFrameBuffer = true; RelativeSizeAxes = Axes.Both; titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); @@ -199,32 +205,6 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { - // We will create the white-to-black gradient by modulating transparency and having - // a black backdrop. This results in an sRGB-space gradient and not linear space, - // transitioning from white to black more perceptually uniformly. - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - // We use a container, such that we can set the colour gradient to go across the - // vertices of the masked container instead of the vertices of the (larger) sprite. - new Container - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0.3f)), - Children = new[] - { - // Zoomed-in and cropped beatmap background - new BeatmapBackgroundSprite(beatmap) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - }, - }, - }, new DifficultyColourBar(starDifficulty) { RelativeSizeAxes = Axes.Y, @@ -329,51 +309,6 @@ namespace osu.Game.Screens.Select addInfoLabels(); } - private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0 - ? new StarRatingDisplay(difficulty) - { - Margin = new MarginPadding { Bottom = 5 } - } - : Empty(); - - private void setMetadata(string source) - { - ArtistLabel.Text = artistBinding.Value; - TitleLabel.Text = string.IsNullOrEmpty(source) ? titleBinding.Value : source + " — " + titleBinding.Value; - ForceRedraw(); - } - - private void addInfoLabels() - { - if (beatmap.Beatmap?.HitObjects?.Any() != true) - return; - - infoLabelContainer.Children = new Drawable[] - { - new InfoLabel(new BeatmapStatistic - { - Name = "Length", - CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), - Content = TimeSpan.FromMilliseconds(beatmap.BeatmapInfo.Length).ToString(@"m\:ss"), - }), - bpmLabelContainer = new Container - { - AutoSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(20, 0), - Children = getRulesetInfoLabels() - } - }; - - settingChangeTracker = new ModSettingChangeTracker(mods); - settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); - - refreshBPMLabel(); - } - private InfoLabel[] getRulesetInfoLabels() { try @@ -426,8 +361,43 @@ namespace osu.Game.Screens.Select CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm), Content = labelText }); + } - ForceRedraw(); + private void setMetadata(string source) + { + ArtistLabel.Text = artistBinding.Value; + TitleLabel.Text = string.IsNullOrEmpty(source) ? titleBinding.Value : source + " — " + titleBinding.Value; + } + + private void addInfoLabels() + { + if (beatmap.Beatmap?.HitObjects?.Any() != true) + return; + + infoLabelContainer.Children = new Drawable[] + { + new InfoLabel(new BeatmapStatistic + { + Name = "Length", + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), + Content = TimeSpan.FromMilliseconds(beatmap.BeatmapInfo.Length).ToString(@"m\:ss"), + }), + bpmLabelContainer = new Container + { + AutoSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(20, 0), + Children = getRulesetInfoLabels() + } + }; + + settingChangeTracker = new ModSettingChangeTracker(mods); + settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); + + refreshBPMLabel(); } private OsuSpriteText[] getMapper(BeatmapMetadata metadata) @@ -450,10 +420,48 @@ namespace osu.Game.Screens.Select }; } - protected override void Dispose(bool isDisposing) + private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0 + ? new StarRatingDisplay(difficulty) + { + Margin = new MarginPadding { Bottom = 5 } + } + : Empty(); + + private class DifficultyColourBar : Container { - base.Dispose(isDisposing); - settingChangeTracker?.Dispose(); + private readonly StarDifficulty difficulty; + + public DifficultyColourBar(StarDifficulty difficulty) + { + this.difficulty = difficulty; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + const float full_opacity_ratio = 0.7f; + + var difficultyColour = colours.ForDifficultyRating(difficulty.DifficultyRating); + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = difficultyColour, + Width = full_opacity_ratio, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Colour = difficultyColour, + Alpha = 0.5f, + X = full_opacity_ratio, + Width = 1 - full_opacity_ratio, + } + }; + } } public class InfoLabel : Container, IHasTooltip @@ -515,41 +523,58 @@ namespace osu.Game.Screens.Select } } - private class DifficultyColourBar : Container + protected override void Dispose(bool isDisposing) { - private readonly StarDifficulty difficulty; + base.Dispose(isDisposing); + settingChangeTracker?.Dispose(); + } + } - public DifficultyColourBar(StarDifficulty difficulty) + public class BufferedWedgeBackground : BufferedContainer + { + private readonly WorkingBeatmap beatmap; + + public BufferedWedgeBackground(WorkingBeatmap beatmap) + : base(pixelSnapping: true) + { + this.beatmap = beatmap; + } + + [BackgroundDependencyLoader] + private void load(LocalisationManager localisation) + { + CacheDrawnFrameBuffer = true; + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] { - this.difficulty = difficulty; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - const float full_opacity_ratio = 0.7f; - - var difficultyColour = colours.ForDifficultyRating(difficulty.DifficultyRating); - - Children = new Drawable[] + // We will create the white-to-black gradient by modulating transparency and having + // a black backdrop. This results in an sRGB-space gradient and not linear space, + // transitioning from white to black more perceptually uniformly. + new Box { - new Box + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + // We use a container, such that we can set the colour gradient to go across the + // vertices of the masked container instead of the vertices of the (larger) sprite. + new Container + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0.3f)), + Children = new[] { - RelativeSizeAxes = Axes.Both, - Colour = difficultyColour, - Width = full_opacity_ratio, + // Zoomed-in and cropped beatmap background + new BeatmapBackgroundSprite(beatmap) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, }, - new Box - { - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Colour = difficultyColour, - Alpha = 0.5f, - X = full_opacity_ratio, - Width = 1 - full_opacity_ratio, - } - }; - } + }, + }; } } } From 6e72ee5f7647eb77b2cc1447e32baeb3d4c990c4 Mon Sep 17 00:00:00 2001 From: Denrage Date: Tue, 20 Apr 2021 10:25:12 +0200 Subject: [PATCH 094/400] Added bindable stardifficulty to StarRatingDisplay --- .../Ranking/Expanded/StarRatingDisplay.cs | 79 +++++++++++++------ osu.Game/Screens/Select/BeatmapInfoWedge.cs | 64 +++++++++------ 2 files changed, 95 insertions(+), 48 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index f7e50fdc8a..081b782034 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Globalization; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -22,7 +24,11 @@ namespace osu.Game.Screens.Ranking.Expanded /// public class StarRatingDisplay : CompositeDrawable { - private readonly StarDifficulty difficulty; + private CircularContainer colorContainer; + private OsuTextFlowContainer textContainer; + + private readonly StarDifficulty starDifficulty; + private readonly IBindable bindableStarDifficulty; /// /// Creates a new using an already computed . @@ -30,14 +36,21 @@ namespace osu.Game.Screens.Ranking.Expanded /// The already computed to display the star difficulty of. public StarRatingDisplay(StarDifficulty starDifficulty) { - difficulty = starDifficulty; + this.starDifficulty = starDifficulty; } - [BackgroundDependencyLoader] - private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) + /// + /// Creates a new using a binded nullable . + /// + /// The binded nullable to display the star difficulty of. If null, a new instance of will be created + public StarRatingDisplay(IBindable starDifficulty) { - AutoSizeAxes = Axes.Both; + bindableStarDifficulty = starDifficulty; + } + private void setDifficulty(OsuColour colours) + { + var difficulty = bindableStarDifficulty == null ? starDifficulty : bindableStarDifficulty.Value ?? new StarDifficulty(); var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); string wholePart = starRatingParts[0]; string fractionPart = starRatingParts[1]; @@ -47,9 +60,36 @@ namespace osu.Game.Screens.Ranking.Expanded ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); + colorContainer.Colour = backgroundColour; + + textContainer.Text = string.Empty; + + textContainer.With(t => + { + t.AddText($"{wholePart}", s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 14); + s.UseFullGlyphHeight = false; + }); + + t.AddText($"{separator}{fractionPart}", s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 7); + s.UseFullGlyphHeight = false; + }); + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) + { + AutoSizeAxes = Axes.Both; + InternalChildren = new Drawable[] { - new CircularContainer + colorContainer = new CircularContainer { RelativeSizeAxes = Axes.Both, Masking = true, @@ -58,7 +98,6 @@ namespace osu.Game.Screens.Ranking.Expanded new Box { RelativeSizeAxes = Axes.Both, - Colour = backgroundColour }, } }, @@ -78,32 +117,24 @@ namespace osu.Game.Screens.Ranking.Expanded Icon = FontAwesome.Solid.Star, Colour = Color4.Black }, - new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) + textContainer = new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, TextAnchor = Anchor.BottomLeft, - }.With(t => - { - t.AddText($"{wholePart}", s => - { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 14); - s.UseFullGlyphHeight = false; - }); - - t.AddText($"{separator}{fractionPart}", s => - { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 7); - s.UseFullGlyphHeight = false; - }); - }) + }, } } }; + + if (bindableStarDifficulty != null) + { + bindableStarDifficulty.BindValueChanged(_ => setDifficulty(colours)); + } + + setDifficulty(colours); } } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 0b72970bb0..9dccdd2897 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -105,7 +105,6 @@ namespace osu.Game.Screens.Select beatmapDifficulty?.UnbindAll(); beatmapDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo, cancellationSource.Token); - beatmapDifficulty.BindValueChanged(_ => updateDisplay()); updateDisplay(); } @@ -151,7 +150,7 @@ namespace osu.Game.Screens.Select removeOldInfo(); Add(Background = loaded); - Add(Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty()) + Add(Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value, beatmapDifficulty) { Shear = -Shear }); @@ -176,15 +175,16 @@ namespace osu.Game.Screens.Select private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; private FillFlowContainer infoLabelContainer; + private Drawable starRatingDisplay; private Container bpmLabelContainer; private ModSettingChangeTracker settingChangeTracker; private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; private readonly IReadOnlyList mods; - private readonly StarDifficulty starDifficulty; + private readonly IBindable starDifficulty; - public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList mods, StarDifficulty difficulty) + public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList mods, IBindable difficulty) { this.beatmap = beatmap; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; @@ -241,12 +241,13 @@ namespace osu.Game.Screens.Select Shear = wedged_container_shear, Children = new[] { - createStarRatingDisplay(starDifficulty).With(display => + starRatingDisplay = new StarRatingDisplay(starDifficulty) { - display.Anchor = Anchor.TopRight; - display.Origin = Anchor.TopRight; - display.Shear = -wedged_container_shear; - }), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Shear = -wedged_container_shear, + Margin = new MarginPadding { Bottom = 5 } + }, StatusPill = new BeatmapSetOnlineStatusPill { Anchor = Anchor.TopRight, @@ -309,6 +310,18 @@ namespace osu.Game.Screens.Select addInfoLabels(); } + private void setStarRatingDisplayVisibility() + { + if (starDifficulty.Value.HasValue && starDifficulty.Value.Value.Stars > 0) + { + starRatingDisplay.Show(); + } + else + { + starRatingDisplay.Hide(); + } + } + private InfoLabel[] getRulesetInfoLabels() { try @@ -420,18 +433,14 @@ namespace osu.Game.Screens.Select }; } - private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0 - ? new StarRatingDisplay(difficulty) - { - Margin = new MarginPadding { Bottom = 5 } - } - : Empty(); - private class DifficultyColourBar : Container { - private readonly StarDifficulty difficulty; + private Box solidDifficultyBox; + private Box transparentDifficultyBox; - public DifficultyColourBar(StarDifficulty difficulty) + private readonly IBindable difficulty; + + public DifficultyColourBar(IBindable difficulty) { this.difficulty = difficulty; } @@ -441,26 +450,33 @@ namespace osu.Game.Screens.Select { const float full_opacity_ratio = 0.7f; - var difficultyColour = colours.ForDifficultyRating(difficulty.DifficultyRating); - Children = new Drawable[] { - new Box + solidDifficultyBox = new Box { RelativeSizeAxes = Axes.Both, - Colour = difficultyColour, Width = full_opacity_ratio, }, - new Box + transparentDifficultyBox = new Box { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - Colour = difficultyColour, Alpha = 0.5f, X = full_opacity_ratio, Width = 1 - full_opacity_ratio, } }; + + difficulty.BindValueChanged(_ => setColour(colours)); + setColour(colours); + } + + private void setColour(OsuColour colours) + { + var difficultyColour = colours.ForDifficultyRating(difficulty.Value?.DifficultyRating ?? (new StarDifficulty()).DifficultyRating); + + solidDifficultyBox.Colour = difficultyColour; + transparentDifficultyBox.Colour = difficultyColour; } } From a683e5ec34cbbbf1fe08a4eafc1cbaeac7d0969a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 17:36:24 +0900 Subject: [PATCH 095/400] Seek using local method --- 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 f6b99c094f..e9a9c83119 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -105,9 +105,10 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { - AdjustableSource.Seek(StartOffset); - AdjustableSource.Stop(); + Seek(StartOffset); + // Manually stop the source in order to not affect the IsPaused state. + AdjustableSource.Stop(); if (!IsPaused.Value) Start(); From ddf1b560f3d4ba22884020838e59c40c50d1b4a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 18:18:50 +0900 Subject: [PATCH 096/400] Remove catcher fade during hyperdash Closes https://github.com/ppy/osu/issues/12472. --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 5d57e84b75..d045dcf16a 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -384,16 +384,7 @@ namespace osu.Game.Rulesets.Catch.UI { updateTrailVisibility(); - if (hyperDashing) - { - this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - } - else - { - this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - } + this.FadeColour(hyperDashing ? hyperDashColour : Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); } private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing; From f11b068dee2b58a0160c138cfcb811e6f18a2ec0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 18:22:58 +0900 Subject: [PATCH 097/400] Allow faster roll speed selection in "Barrel Roll" mod --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index aee431284e..1587f97f46 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods public BindableNumber SpinSpeed { get; } = new BindableDouble(0.5) { MinValue = 0.02, - MaxValue = 4, + MaxValue = 12, Precision = 0.01, }; From ec080fcb32fdf2fa1ca7fd4b2d8db1c7a4d2ffc7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 18:25:46 +0900 Subject: [PATCH 098/400] Move seekOffset back to MasterGameplayClockContainer --- osu.Game/Screens/Play/GameplayClockContainer.cs | 7 +------ .../Screens/Play/MasterGameplayClockContainer.cs | 12 +++++++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index e9a9c83119..5cd17d92c4 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -29,11 +29,6 @@ namespace osu.Game.Screens.Play /// protected readonly DecoupleableInterpolatingFramedClock AdjustableSource; - /// - /// The offset at which to start playing. Affects the time which the clock is reset to via . - /// - protected virtual double StartOffset => 0; - /// /// The source clock. /// @@ -105,7 +100,7 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { - Seek(StartOffset); + Seek(0); // Manually stop the source in order to not affect the IsPaused state. AdjustableSource.Stop(); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 1b893a76c5..affe24069d 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -47,9 +47,6 @@ namespace osu.Game.Screens.Play private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); - protected override double StartOffset => startOffset; - private double startOffset; - private readonly WorkingBeatmap beatmap; private readonly double gameplayStartTime; private readonly bool startAtGameplayStart; @@ -59,6 +56,7 @@ namespace osu.Game.Screens.Play private FramedOffsetClock platformOffsetClock; private LocalGameplayClock localGameplayClock; private Bindable userAudioOffset; + private double startOffset; public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) : base(beatmap.Track) @@ -94,6 +92,8 @@ namespace osu.Game.Screens.Play if (beatmap.BeatmapInfo.AudioLeadIn > 0) startOffset = Math.Min(startOffset, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); } + + Seek(startOffset); } protected override void OnIsPausedChanged(ValueChangedEvent isPaused) @@ -142,6 +142,12 @@ namespace osu.Game.Screens.Play Seek(skipTarget); } + public override void Reset() + { + base.Reset(); + Seek(startOffset); + } + protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) { // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. From f144661c31a729b6874fad1b720035e3e95d4d23 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 18:26:30 +0900 Subject: [PATCH 099/400] Fix storyboard sample test scene --- .../Gameplay/TestSceneStoryboardSamples.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 5b8abddd3f..bbab9ae94d 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -67,15 +68,17 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayContainer = new MasterGameplayClockContainer(working, 0)); - - gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + Add(gameplayContainer = new MasterGameplayClockContainer(working, 0) { - Clock = gameplayContainer.GameplayClock + IsPaused = { Value = true }, + Child = new FrameStabilityContainer + { + Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + } }); }); - AddStep("start time", () => gameplayContainer.Start()); + AddStep("reset clock", () => gameplayContainer.Start()); AddUntilStep("sample played", () => sample.RequestedPlaying); AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue); @@ -92,11 +95,13 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayContainer = new MasterGameplayClockContainer(working, 1000, true)); - - gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + Add(gameplayContainer = new MasterGameplayClockContainer(working, 1000, true) { - Clock = gameplayContainer.GameplayClock + IsPaused = { Value = true }, + Child = new FrameStabilityContainer + { + Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + } }); }); From ac0ed72d043d339a856637a0a3675fe06f5280ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 18:36:11 +0900 Subject: [PATCH 100/400] Keep hitcircles aligned with view in "Barrel Roll" mod --- .../Mods/OsuModBarrelRoll.cs | 25 +++++++++++++++++-- .../Objects/Drawables/DrawableHitCircle.cs | 6 ++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index aee431284e..64b87f3977 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -1,20 +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.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset + public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToDrawableHitObjects { + private float currentRotation; + [SettingSource("Roll speed", "Rotations per minute")] public BindableNumber SpinSpeed { get; } = new BindableDouble(0.5) { @@ -35,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - playfield.Rotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); + playfield.Rotation = currentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -43,5 +48,21 @@ namespace osu.Game.Rulesets.Osu.Mods // scale the playfield to allow all hitobjects to stay within the visible region. drawableRuleset.Playfield.Scale = new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X); } + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var d in drawables) + { + d.OnUpdate += _ => + { + switch (d) + { + case DrawableHitCircle circle: + circle.CirclePiece.Rotation = -currentRotation; + break; + } + }; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 189003875d..df77ec2693 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -66,7 +66,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()), + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, ApproachCircle = new ApproachCircle { Alpha = 0, From c5d6b6ea8d05578cfac4ae8697f15e37d8d84da3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 18:41:09 +0900 Subject: [PATCH 101/400] Fix tests failing intermittently This was due to this code happening in UpdateAfterChildren(), after the GCC has processed one frame. During this time, the clock could have advanced an arbitrary amount. The cause of this is the removal of the Task.Run() to set the clock in Restart() (now called Reset()) which changed the timing, so it only worked before due to pure luck. --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index dccde366c2..f5f17a0bc1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -130,9 +130,9 @@ namespace osu.Game.Tests.Visual.Gameplay public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime; - protected override void UpdateAfterChildren() + protected override void Update() { - base.UpdateAfterChildren(); + base.Update(); if (!FirstFrameClockTime.HasValue) { From 4e6cd8082ea784ee4223f2f36e7c05f1253aeac5 Mon Sep 17 00:00:00 2001 From: Denrage Date: Tue, 20 Apr 2021 12:00:04 +0200 Subject: [PATCH 102/400] WIP refresh BPM-Label on mod change --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 27 +++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 9dccdd2897..b3aa4f0ba5 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Select removeOldInfo(); Add(Background = loaded); - Add(Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value, beatmapDifficulty) + Add(Info = new WedgeInfoText(beatmap, ruleset.Value, mods, beatmapDifficulty) { Shear = -Shear }); @@ -181,10 +181,10 @@ namespace osu.Game.Screens.Select private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; - private readonly IReadOnlyList mods; + private readonly IBindable> mods; private readonly IBindable starDifficulty; - public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList mods, IBindable difficulty) + public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IBindable> mods, IBindable difficulty) { this.beatmap = beatmap; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; @@ -300,14 +300,16 @@ namespace osu.Game.Screens.Select } }; + addInfoLabels(); + titleBinding.BindValueChanged(_ => setMetadata(metadata.Source)); artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true); + starDifficulty.BindValueChanged(_ => setStarRatingDisplayVisibility(), true); // no difficulty means it can't have a status to show if (beatmapInfo.Version == null) StatusPill.Hide(); - addInfoLabels(); } private void setStarRatingDisplayVisibility() @@ -349,7 +351,7 @@ namespace osu.Game.Screens.Select return Array.Empty(); } - private void refreshBPMLabel() + private void refreshBPMLabel(IReadOnlyList mods) { var b = beatmap.Beatmap; if (b == null) @@ -407,10 +409,16 @@ namespace osu.Game.Screens.Select } }; - settingChangeTracker = new ModSettingChangeTracker(mods); - settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); + // this is currently not triggering when a mod gets (de)selected + mods.BindValueChanged(mods => refreshModInformation(mods), true); + } - refreshBPMLabel(); + private void refreshModInformation(ValueChangedEvent> modsChangedEvent) + { + settingChangeTracker?.Dispose(); + settingChangeTracker = new ModSettingChangeTracker(modsChangedEvent.NewValue); + settingChangeTracker.SettingChanged += _ => refreshBPMLabel(modsChangedEvent.NewValue); + refreshBPMLabel(mods.Value); } private OsuSpriteText[] getMapper(BeatmapMetadata metadata) @@ -467,8 +475,7 @@ namespace osu.Game.Screens.Select } }; - difficulty.BindValueChanged(_ => setColour(colours)); - setColour(colours); + difficulty.BindValueChanged(_ => setColour(colours), true); } private void setColour(OsuColour colours) From c5d35ab78733cdd64322467e378474e526096a83 Mon Sep 17 00:00:00 2001 From: Denrage Date: Tue, 20 Apr 2021 12:35:31 +0200 Subject: [PATCH 103/400] removed mods binding passthrough --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index b3aa4f0ba5..c308f14f74 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -41,9 +41,6 @@ namespace osu.Game.Screens.Select [Resolved] private IBindable ruleset { get; set; } - [Resolved] - private IBindable> mods { get; set; } - [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } @@ -150,7 +147,7 @@ namespace osu.Game.Screens.Select removeOldInfo(); Add(Background = loaded); - Add(Info = new WedgeInfoText(beatmap, ruleset.Value, mods, beatmapDifficulty) + Add(Info = new WedgeInfoText(beatmap, ruleset.Value, beatmapDifficulty) { Shear = -Shear }); @@ -172,6 +169,9 @@ namespace osu.Game.Screens.Select public OsuSpriteText VersionLabel { get; private set; } public BeatmapSetOnlineStatusPill StatusPill { get; private set; } + [Resolved] + private IBindable> mods { get; set; } + private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; private FillFlowContainer infoLabelContainer; @@ -181,14 +181,12 @@ namespace osu.Game.Screens.Select private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; - private readonly IBindable> mods; private readonly IBindable starDifficulty; - public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IBindable> mods, IBindable difficulty) + public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IBindable difficulty) { this.beatmap = beatmap; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; - this.mods = mods; starDifficulty = difficulty; } @@ -409,7 +407,6 @@ namespace osu.Game.Screens.Select } }; - // this is currently not triggering when a mod gets (de)selected mods.BindValueChanged(mods => refreshModInformation(mods), true); } @@ -418,7 +415,7 @@ namespace osu.Game.Screens.Select settingChangeTracker?.Dispose(); settingChangeTracker = new ModSettingChangeTracker(modsChangedEvent.NewValue); settingChangeTracker.SettingChanged += _ => refreshBPMLabel(modsChangedEvent.NewValue); - refreshBPMLabel(mods.Value); + refreshBPMLabel(modsChangedEvent.NewValue); } private OsuSpriteText[] getMapper(BeatmapMetadata metadata) From 5262d94e21e630c794c8a6356d12f44f38ce33a0 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:21:57 +0200 Subject: [PATCH 104/400] Fix wrong assert in offscreen test --- .../Editor/Checks/CheckOffscreenObjectsTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index 3a4817398c..6139b0e676 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks [Test] public void TestSliderNearEdgeStackedOffscreen() { - assertOk(new Beatmap + assertOffscreenSlider(new Beatmap { HitObjects = new List { From 6a1e4ff99f41091d04f82bb721525af410eb10b7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:28:32 +0200 Subject: [PATCH 105/400] Add file hash to file presence test Necessary because we now find the storage path of the file rather than just the file itself. --- osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index fe2e16ffd8..f6e875a8fc 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.IO; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; using osu.Game.Tests.Beatmaps; @@ -30,7 +31,11 @@ namespace osu.Game.Tests.Editing.Checks { Files = new List(new[] { - new BeatmapSetFileInfo { Filename = "abc123.jpg" } + new BeatmapSetFileInfo + { + Filename = "abc123.jpg", + FileInfo = new FileInfo { Hash = "abcdef" } + } }) } } From c0318a4d3ef401f4b3288d14ac4188f995a5f0d1 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:29:14 +0200 Subject: [PATCH 106/400] Fix usage of _ in Moq lambdas --- osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs | 10 +++++----- .../Editing/Checks/CheckBackgroundQualityTest.cs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs index e73d9adfb0..7658ca728d 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Editing.Checks beatmap.Metadata.AudioFile = null; var mock = new Mock(); - mock.SetupGet(_ => _.Beatmap).Returns(beatmap); - mock.SetupGet(_ => _.Track).Returns((Track)null); + mock.SetupGet(w => w.Beatmap).Returns(beatmap); + mock.SetupGet(w => w.Track).Returns((Track)null); Assert.That(check.Run(beatmap, mock.Object), Is.Empty); } @@ -102,11 +102,11 @@ namespace osu.Game.Tests.Editing.Checks private Mock getMockWorkingBeatmap(int? audioBitrate) { var mockTrack = new Mock(); - mockTrack.SetupGet(_ => _.Bitrate).Returns(audioBitrate); + mockTrack.SetupGet(t => t.Bitrate).Returns(audioBitrate); var mockWorkingBeatmap = new Mock(); - mockWorkingBeatmap.SetupGet(_ => _.Beatmap).Returns(beatmap); - mockWorkingBeatmap.SetupGet(_ => _.Track).Returns(mockTrack.Object); + mockWorkingBeatmap.SetupGet(w => w.Beatmap).Returns(beatmap); + mockWorkingBeatmap.SetupGet(w => w.Track).Returns(mockTrack.Object); return mockWorkingBeatmap; } diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index b6970a99e4..f0f972d2fa 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -120,9 +120,9 @@ namespace osu.Game.Tests.Editing.Checks var stream = new MemoryStream(fileBytes ?? new byte[1024 * 1024]); var mock = new Mock(); - mock.SetupGet(_ => _.Beatmap).Returns(beatmap); - mock.SetupGet(_ => _.Background).Returns(background); - mock.Setup(_ => _.GetStream(It.IsAny())).Returns(stream); + mock.SetupGet(w => w.Beatmap).Returns(beatmap); + mock.SetupGet(w => w.Background).Returns(background); + mock.Setup(w => w.GetStream(It.IsAny())).Returns(stream); return mock; } From 3e1b6b3b346ea449a3504eb71961f7446bf84379 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:34:12 +0200 Subject: [PATCH 107/400] Simplify verifier run call args Uses the resolved working beatmap instead of resolving it every time. Also uses the EditorBeatmap itself as playable beatmap, as it is of type `IBeatmap` already, and `.PlayableBeatmap` forwards everything anyway. --- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 530f24300b..9de1f04271 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Edit.Verify private EditorClock clock { get; set; } [Resolved] - private BeatmapManager beatmapManager { get; set; } + private IBindable workingBeatmap { get; set; } [Resolved] private EditorBeatmap beatmap { get; set; } @@ -122,11 +122,10 @@ namespace osu.Game.Screens.Edit.Verify private void refresh() { - var workingBeatmap = beatmapManager.GetWorkingBeatmap(beatmap.BeatmapInfo); - var issues = generalVerifier.Run(beatmap.PlayableBeatmap, workingBeatmap); + var issues = generalVerifier.Run(beatmap, workingBeatmap.Value); if (rulesetVerifier != null) - issues = issues.Concat(rulesetVerifier.Run(beatmap.PlayableBeatmap, workingBeatmap)); + issues = issues.Concat(rulesetVerifier.Run(beatmap, workingBeatmap.Value)); table.Issues = issues .OrderBy(issue => issue.Template.Type) From d7a81471c85a4aff21279974092847c4331355aa Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:40:38 +0200 Subject: [PATCH 108/400] Add xmldoc to `GetPathForFile` --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 774bd0bc62..d90ede5d4b 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -59,6 +59,10 @@ namespace osu.Game.Beatmaps public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + /// + /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. + /// + /// The name of the file to get the storage path of. public string GetPathForFile(string filename) => Files?.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; public List Files { get; set; } From e9dfa2860a4fd63f61f0ad2a26d9677d6b4498fb Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:44:06 +0200 Subject: [PATCH 109/400] Add xmldoc note about path being relative --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index d90ede5d4b..1ce42535a0 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -61,6 +61,7 @@ namespace osu.Game.Beatmaps /// /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. + /// The path returned is relative to the user file storage. /// /// The name of the file to get the storage path of. public string GetPathForFile(string filename) => Files?.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; From f799a6e733e0d2075f7feafd9333cfaf5c604fad Mon Sep 17 00:00:00 2001 From: Denrage Date: Tue, 20 Apr 2021 14:18:41 +0200 Subject: [PATCH 110/400] Removed StarDifficulty binding passthrough --- .../Ranking/Expanded/StarRatingDisplay.cs | 42 +++++++---- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 69 ++++++++++--------- 2 files changed, 67 insertions(+), 44 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 081b782034..1aa8f5ca73 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -3,6 +3,7 @@ using System; using System.Globalization; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -24,11 +25,16 @@ namespace osu.Game.Screens.Ranking.Expanded /// public class StarRatingDisplay : CompositeDrawable { + [Resolved] + private OsuColour colours { get; set; } + private CircularContainer colorContainer; private OsuTextFlowContainer textContainer; + private CancellationTokenSource cancellationTokenSource; + private IBindable bindableStarDifficulty; private readonly StarDifficulty starDifficulty; - private readonly IBindable bindableStarDifficulty; + private readonly BeatmapInfo beatmapInfo; /// /// Creates a new using an already computed . @@ -40,17 +46,16 @@ namespace osu.Game.Screens.Ranking.Expanded } /// - /// Creates a new using a binded nullable . + /// Creates a new using a to use a bindable for the difficulty. /// - /// The binded nullable to display the star difficulty of. If null, a new instance of will be created - public StarRatingDisplay(IBindable starDifficulty) + /// The to use to create a bindable for + public StarRatingDisplay(BeatmapInfo beatmapInfo) { - bindableStarDifficulty = starDifficulty; + this.beatmapInfo = beatmapInfo; } - private void setDifficulty(OsuColour colours) + private void setDifficulty(StarDifficulty difficulty) { - var difficulty = bindableStarDifficulty == null ? starDifficulty : bindableStarDifficulty.Value ?? new StarDifficulty(); var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); string wholePart = starRatingParts[0]; string fractionPart = starRatingParts[1]; @@ -83,8 +88,17 @@ namespace osu.Game.Screens.Ranking.Expanded } [BackgroundDependencyLoader] - private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) + private void load(BeatmapDifficultyCache difficultyCache) { + if (beatmapInfo != null) + { + cancellationTokenSource?.Cancel(); + cancellationTokenSource = new CancellationTokenSource(); + + bindableStarDifficulty?.UnbindAll(); + bindableStarDifficulty = difficultyCache.GetBindableDifficulty(beatmapInfo, cancellationTokenSource.Token); + } + AutoSizeAxes = Axes.Both; InternalChildren = new Drawable[] @@ -130,11 +144,15 @@ namespace osu.Game.Screens.Ranking.Expanded }; if (bindableStarDifficulty != null) - { - bindableStarDifficulty.BindValueChanged(_ => setDifficulty(colours)); - } + bindableStarDifficulty.BindValueChanged(valueChanged => setDifficulty(valueChanged.NewValue ?? new StarDifficulty()), true); + else + setDifficulty(starDifficulty); + } - setDifficulty(colours); + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + cancellationTokenSource?.Cancel(); } } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index c308f14f74..e3afd53210 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -41,11 +41,6 @@ namespace osu.Game.Screens.Select [Resolved] private IBindable ruleset { get; set; } - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } - - private IBindable beatmapDifficulty; - protected BufferedWedgeBackground Background; protected WedgeInfoText Info; @@ -87,8 +82,6 @@ namespace osu.Game.Screens.Select private WorkingBeatmap beatmap; - private CancellationTokenSource cancellationSource; - public WorkingBeatmap Beatmap { get => beatmap; @@ -97,12 +90,6 @@ namespace osu.Game.Screens.Select if (beatmap == value) return; beatmap = value; - cancellationSource?.Cancel(); - cancellationSource = new CancellationTokenSource(); - - beatmapDifficulty?.UnbindAll(); - beatmapDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo, cancellationSource.Token); - updateDisplay(); } } @@ -147,7 +134,7 @@ namespace osu.Game.Screens.Select removeOldInfo(); Add(Background = loaded); - Add(Info = new WedgeInfoText(beatmap, ruleset.Value, beatmapDifficulty) + Add(Info = new WedgeInfoText(beatmap, ruleset.Value) { Shear = -Shear }); @@ -158,7 +145,6 @@ namespace osu.Game.Screens.Select protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - cancellationSource?.Cancel(); } public class WedgeInfoText : Container @@ -178,24 +164,30 @@ namespace osu.Game.Screens.Select private Drawable starRatingDisplay; private Container bpmLabelContainer; private ModSettingChangeTracker settingChangeTracker; + private CancellationTokenSource cancellationTokenSource; + private IBindable starDifficulty; private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; - private readonly IBindable starDifficulty; - public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IBindable difficulty) + public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset) { this.beatmap = beatmap; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; - starDifficulty = difficulty; } [BackgroundDependencyLoader] - private void load(LocalisationManager localisation) + private void load(LocalisationManager localisation, BeatmapDifficultyCache difficultyCache) { var beatmapInfo = beatmap.BeatmapInfo; var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); + cancellationTokenSource?.Cancel(); + cancellationTokenSource = new CancellationTokenSource(); + + starDifficulty?.UnbindAll(); + starDifficulty = difficultyCache.GetBindableDifficulty(beatmapInfo, cancellationTokenSource.Token); + RelativeSizeAxes = Axes.Both; titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); @@ -203,7 +195,7 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { - new DifficultyColourBar(starDifficulty) + new DifficultyColourBar(beatmapInfo) { RelativeSizeAxes = Axes.Y, Width = 20, @@ -239,7 +231,7 @@ namespace osu.Game.Screens.Select Shear = wedged_container_shear, Children = new[] { - starRatingDisplay = new StarRatingDisplay(starDifficulty) + starRatingDisplay = new StarRatingDisplay(beatmapInfo) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -313,13 +305,9 @@ namespace osu.Game.Screens.Select private void setStarRatingDisplayVisibility() { if (starDifficulty.Value.HasValue && starDifficulty.Value.Value.Stars > 0) - { starRatingDisplay.Show(); - } else - { starRatingDisplay.Hide(); - } } private InfoLabel[] getRulesetInfoLabels() @@ -440,21 +428,32 @@ namespace osu.Game.Screens.Select private class DifficultyColourBar : Container { + [Resolved] + private OsuColour colours { get; set; } + private Box solidDifficultyBox; private Box transparentDifficultyBox; + private CancellationTokenSource cancellationTokenSource; + private IBindable starDifficulty; - private readonly IBindable difficulty; + private readonly BeatmapInfo beatmapInfo; - public DifficultyColourBar(IBindable difficulty) + public DifficultyColourBar(BeatmapInfo beatmapInfo) { - this.difficulty = difficulty; + this.beatmapInfo = beatmapInfo; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(BeatmapDifficultyCache difficultyCache) { const float full_opacity_ratio = 0.7f; + cancellationTokenSource?.Cancel(); + cancellationTokenSource = new CancellationTokenSource(); + + starDifficulty?.UnbindAll(); + starDifficulty = difficultyCache.GetBindableDifficulty(beatmapInfo, cancellationTokenSource.Token); + Children = new Drawable[] { solidDifficultyBox = new Box @@ -472,16 +471,22 @@ namespace osu.Game.Screens.Select } }; - difficulty.BindValueChanged(_ => setColour(colours), true); + starDifficulty.BindValueChanged(valueChangedEvent => setColour(valueChangedEvent), true); } - private void setColour(OsuColour colours) + private void setColour(ValueChangedEvent valueChanged) { - var difficultyColour = colours.ForDifficultyRating(difficulty.Value?.DifficultyRating ?? (new StarDifficulty()).DifficultyRating); + var difficultyColour = colours.ForDifficultyRating(valueChanged.NewValue?.DifficultyRating ?? (new StarDifficulty()).DifficultyRating); solidDifficultyBox.Colour = difficultyColour; transparentDifficultyBox.Colour = difficultyColour; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + cancellationTokenSource?.Cancel(); + } } public class InfoLabel : Container, IHasTooltip From a9e4a0ed50dcfbd81487afbc14ba10fd8cd7463f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 21:17:24 +0900 Subject: [PATCH 111/400] Fix potentially starting play when finished The UserFinishedPlaying event may trigger before the event is subscribed to by SpectatorScreen. For such cases, an extra check is done to make sure the user is _actually_ playing. --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index f554b15abf..ed01d56801 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -127,6 +127,10 @@ namespace osu.Game.Screens.Spectate if (!userMap.ContainsKey(userId)) return; + // The user may have stopped playing. + if (!spectatorClient.TryGetPlayingUserState(userId, out _)) + return; + Schedule(() => OnUserStateChanged(userId, state)); updateGameplayState(userId); From 4cc3321d54b2e2e57ebb09b1cb240135d421ec4d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 21:20:08 +0900 Subject: [PATCH 112/400] Fix potential doubling of events --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 13b12d9add..378096c7fb 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -303,13 +303,14 @@ namespace osu.Game.Online.Spectator /// Whether the action provided in should be run once immediately for all users currently playing. public void BindUserBeganPlaying(Action callback, bool runOnceImmediately = false) { - OnUserBeganPlaying += callback; - - if (!runOnceImmediately) - return; - + // The lock is taken before the event is subscribed to to prevent doubling of events. lock (userLock) { + OnUserBeganPlaying += callback; + + if (!runOnceImmediately) + return; + foreach (var (userId, state) in playingUserStates) callback(userId, state); } From df29e61147fd45f0f310453c19393266d10609dd Mon Sep 17 00:00:00 2001 From: Denrage Date: Tue, 20 Apr 2021 14:22:49 +0200 Subject: [PATCH 113/400] Fix CodeFactor error --- 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 e3afd53210..44dbd5a5c6 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -299,7 +299,6 @@ namespace osu.Game.Screens.Select // no difficulty means it can't have a status to show if (beatmapInfo.Version == null) StatusPill.Hide(); - } private void setStarRatingDisplayVisibility() From 583754b22a430dba73db9688d106dd7d734d6be4 Mon Sep 17 00:00:00 2001 From: Denrage Date: Tue, 20 Apr 2021 14:29:53 +0200 Subject: [PATCH 114/400] Removed unnecessary whitespaces --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 44dbd5a5c6..7cb6f2caf3 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -598,7 +598,7 @@ namespace osu.Game.Screens.Select }, }, }, - }; + }; } } } From e9571b72cf149a1941717bd1a1e9a22015feca20 Mon Sep 17 00:00:00 2001 From: Denrage Date: Tue, 20 Apr 2021 14:53:35 +0200 Subject: [PATCH 115/400] Fixed InspectCode --- osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs | 3 +-- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 9 ++------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 1aa8f5ca73..a3e9336648 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.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.Globalization; using System.Threading; using osu.Framework.Allocation; @@ -46,7 +45,7 @@ namespace osu.Game.Screens.Ranking.Expanded } /// - /// Creates a new using a to use a bindable for the difficulty. + /// Creates a new using a to use a bindable for the difficulty. /// /// The to use to create a bindable for public StarRatingDisplay(BeatmapInfo beatmapInfo) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 7cb6f2caf3..9077c115d4 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -142,11 +142,6 @@ namespace osu.Game.Screens.Select } } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - } - public class WedgeInfoText : Container { public FillFlowContainer MapperContainer { get; private set; } @@ -394,7 +389,7 @@ namespace osu.Game.Screens.Select } }; - mods.BindValueChanged(mods => refreshModInformation(mods), true); + mods.BindValueChanged(refreshModInformation, true); } private void refreshModInformation(ValueChangedEvent> modsChangedEvent) @@ -470,7 +465,7 @@ namespace osu.Game.Screens.Select } }; - starDifficulty.BindValueChanged(valueChangedEvent => setColour(valueChangedEvent), true); + starDifficulty.BindValueChanged(setColour, true); } private void setColour(ValueChangedEvent valueChanged) From 7fc450c620eba103fc96a716edd149c9e15e790a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 23:42:53 +0900 Subject: [PATCH 116/400] Fix mod settings blocking input outside its visible area Closes #12502. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 26b8632d7f..754b260bf0 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -245,15 +245,21 @@ namespace osu.Game.Overlays.Mods }, } }, - ModSettingsContainer = new ModSettingsContainer + new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Width = 0.3f, - Alpha = 0, Padding = new MarginPadding(30), - SelectedMods = { BindTarget = SelectedMods }, + Width = 0.3f, + Children = new Drawable[] + { + ModSettingsContainer = new ModSettingsContainer + { + Alpha = 0, + SelectedMods = { BindTarget = SelectedMods }, + }, + } }, } }, From 4910d8f56cd04339773ec2ae4ca288dcc9324839 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 23:57:12 +0900 Subject: [PATCH 117/400] Fix click-to-resume cursor location being incorrect when playfield is transformed Closes #12501. --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 44ca5e850f..27d48d1296 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.UI base.PopIn(); GameplayCursor.ActiveCursor.Hide(); - cursorScaleContainer.MoveTo(GameplayCursor.ActiveCursor.Position); + cursorScaleContainer.Position = ToLocalSpace(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre); clickToResumeCursor.Appear(); if (localCursorContainer == null) From 881043bc5d8d8a76d3fd54c9a389adc83472ee61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Apr 2021 19:05:43 +0200 Subject: [PATCH 118/400] Fix failing test after mod settings layout changes The slight hack which was used in the test to ensure that the mod settings overlay covered the entire width of the mod overlay broke after adjustments to the layout in the previous commit. Locally adjust the hack to use the parent of the `ModSettingsContainer` rather than the container itself. --- osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 2158cf77e5..bda1973354 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.UserInterface GetModButton(mod).SelectNext(1); public void SetModSettingsWidth(float newWidth) => - ModSettingsContainer.Width = newWidth; + ModSettingsContainer.Parent.Width = newWidth; } public class TestRulesetInfo : RulesetInfo From e80c3c317a34b989dc18a9d904d39da724c828f3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 09:14:22 +0900 Subject: [PATCH 119/400] Rename UnmanagedHitObjectEntry -> SyntheticHitObjectEntry "Unmanaged" was confusing because its lifetime is still managed by the HitObjectContainer. --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- ...{UnmanagedHitObjectEntry.cs => SyntheticHitObjectEntry.cs} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Rulesets/Objects/{UnmanagedHitObjectEntry.cs => SyntheticHitObjectEntry.cs} (81%) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d798eba4e1..ca1c601c1d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { if (initialHitObject != null) { - lifetimeEntry = new UnmanagedHitObjectEntry(initialHitObject); + lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject); ensureEntryHasResult(); } } @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (hitObject == null) throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); - Apply(new UnmanagedHitObjectEntry(hitObject)); + Apply(new SyntheticHitObjectEntry(hitObject)); } /// diff --git a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs similarity index 81% rename from osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs rename to osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs index 3cfa3c4d3a..76f9eaf25a 100644 --- a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs +++ b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs @@ -9,9 +9,9 @@ namespace osu.Game.Rulesets.Objects /// Created for a when only is given /// to make sure a is always associated with a . /// - internal class UnmanagedHitObjectEntry : HitObjectLifetimeEntry + internal class SyntheticHitObjectEntry : HitObjectLifetimeEntry { - public UnmanagedHitObjectEntry(HitObject hitObject) + public SyntheticHitObjectEntry(HitObject hitObject) : base(hitObject) { } From 67fcfd9dbc243a8ffe976410729efeb94c06336d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 09:48:16 +0900 Subject: [PATCH 120/400] Fix wrong InitialLifetimeOffset is used for a non-pooled DHO. HitObjectLifetimeEntry's InitialLifetimeOffset is different from DrawableHitObject's InitialLifetimeOffset. --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs | 9 +++++++-- osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs | 6 ++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ca1c601c1d..4eea058163 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { if (initialHitObject != null) { - lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject); + lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject, initialHitObject.StartTime - InitialLifetimeOffset); ensureEntryHasResult(); } } @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (hitObject == null) throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); - Apply(new SyntheticHitObjectEntry(hitObject)); + Apply(new SyntheticHitObjectEntry(hitObject, hitObject.StartTime - InitialLifetimeOffset)); } /// diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 1954d7e6d2..d1d459a8f6 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -30,12 +30,17 @@ namespace osu.Game.Rulesets.Objects /// Creates a new . /// /// The to store the lifetime of. - public HitObjectLifetimeEntry(HitObject hitObject) + /// The . + /// The . + public HitObjectLifetimeEntry(HitObject hitObject, double lifetimeStart = double.MinValue, double lifetimeEnd = double.MaxValue) { HitObject = hitObject; startTimeBindable.BindTo(HitObject.StartTimeBindable); - startTimeBindable.BindValueChanged(onStartTimeChanged, true); + // Only set initial lifetime if it is not provided + startTimeBindable.BindValueChanged(onStartTimeChanged, lifetimeStart == double.MinValue); + + setLifetime(lifetimeStart, lifetimeEnd); } // The lifetime start, as set by the hitobject. diff --git a/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs index 76f9eaf25a..c064f3ff10 100644 --- a/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs +++ b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.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. +#nullable enable + using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Objects @@ -11,8 +13,8 @@ namespace osu.Game.Rulesets.Objects /// internal class SyntheticHitObjectEntry : HitObjectLifetimeEntry { - public SyntheticHitObjectEntry(HitObject hitObject) - : base(hitObject) + public SyntheticHitObjectEntry(HitObject hitObject, double initialLifetimeStart) + : base(hitObject, initialLifetimeStart) { } } From 44ff08cce4675e40492364633a1917608f7c908b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 10:02:50 +0900 Subject: [PATCH 121/400] Revert "Fix wrong InitialLifetimeOffset is used for a non-pooled DHO." This reverts commit 67fcfd9d --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs | 9 ++------- osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs | 6 ++---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4eea058163..ca1c601c1d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { if (initialHitObject != null) { - lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject, initialHitObject.StartTime - InitialLifetimeOffset); + lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject); ensureEntryHasResult(); } } @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (hitObject == null) throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); - Apply(new SyntheticHitObjectEntry(hitObject, hitObject.StartTime - InitialLifetimeOffset)); + Apply(new SyntheticHitObjectEntry(hitObject)); } /// diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index d1d459a8f6..1954d7e6d2 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -30,17 +30,12 @@ namespace osu.Game.Rulesets.Objects /// Creates a new . /// /// The to store the lifetime of. - /// The . - /// The . - public HitObjectLifetimeEntry(HitObject hitObject, double lifetimeStart = double.MinValue, double lifetimeEnd = double.MaxValue) + public HitObjectLifetimeEntry(HitObject hitObject) { HitObject = hitObject; startTimeBindable.BindTo(HitObject.StartTimeBindable); - // Only set initial lifetime if it is not provided - startTimeBindable.BindValueChanged(onStartTimeChanged, lifetimeStart == double.MinValue); - - setLifetime(lifetimeStart, lifetimeEnd); + startTimeBindable.BindValueChanged(onStartTimeChanged, true); } // The lifetime start, as set by the hitobject. diff --git a/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs index c064f3ff10..76f9eaf25a 100644 --- a/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs +++ b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.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. -#nullable enable - using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Objects @@ -13,8 +11,8 @@ namespace osu.Game.Rulesets.Objects /// internal class SyntheticHitObjectEntry : HitObjectLifetimeEntry { - public SyntheticHitObjectEntry(HitObject hitObject, double initialLifetimeStart) - : base(hitObject, initialLifetimeStart) + public SyntheticHitObjectEntry(HitObject hitObject) + : base(hitObject) { } } From 9d423201edb4eebdc23fe141830773db5b2729f8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 10:29:18 +0900 Subject: [PATCH 122/400] Fix slider tails wiggling independently --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 9c5e41f245..123bbe12de 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Mods // Wiggle the repeat points with the slider instead of independently. // Also fixes an issue with repeat points being positioned incorrectly. - if (osuObject is SliderRepeat) + if (osuObject is SliderRepeat || osuObject is SliderTailCircle) return; Random objRand = new Random((int)osuObject.StartTime); From e454037d82e5883e2419203ccb98a983e333cece Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 10:32:08 +0900 Subject: [PATCH 123/400] Add to comment --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 123bbe12de..a01cec4bb3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Mods var osuObject = (OsuHitObject)drawable.HitObject; Vector2 origin = drawable.Position; - // Wiggle the repeat points with the slider instead of independently. + // Wiggle the repeat points and the tail with the slider instead of independently. // Also fixes an issue with repeat points being positioned incorrectly. if (osuObject is SliderRepeat || osuObject is SliderTailCircle) return; From 73d3da168769bf884ec876cdf888aa6f118a8dcb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 11:32:01 +0900 Subject: [PATCH 124/400] Fix wrong InitialLifetimeOffset is used for a non-pooled DHO. HitObjectLifetimeEntry's InitialLifetimeOffset is different from DrawableHitObject's InitialLifetimeOffset. --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ca1c601c1d..d0fa7eb22f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -239,6 +239,11 @@ namespace osu.Game.Rulesets.Objects.Drawables lifetimeEntry = newEntry; + // LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset. + // We override this with DHO's InitialLifetimeOffset for a non-pooled DHO. + if (newEntry is SyntheticHitObjectEntry) + lifetimeEntry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + LifetimeStart = lifetimeEntry.LifetimeStart; LifetimeEnd = lifetimeEntry.LifetimeEnd; From 3fbeadf31847cd87aef622e7ddba08cbb253f9ec Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 14:32:37 +0900 Subject: [PATCH 125/400] Deprecate old overload of Apply --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs | 2 +- osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs | 4 ++-- .../TestSceneDrumRollApplication.cs | 4 ++-- osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs | 4 ++-- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +---- osu.Game/Rulesets/UI/Playfield.cs | 2 +- 8 files changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs index 5fc1082743..8b3fead366 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Position = new Vector2(128, 128), ComboIndex = 1, - }), null)); + }))); } private HitCircle prepareObject(HitCircle circle) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs index aac6db60fe..e698766aac 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(300, 0), }), RepeatCount = 1 - }), null)); + }))); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs index d7fbc7ac48..8c97c02049 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), ComboIndex = 1, Duration = 1000, - }), null)); + }))); AddAssert("rotation is reset", () => dho.Result.RateAdjustedRotation == 0); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs index a970965141..f33c738b04 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { StartTime = 400, Major = true - }), null)); + }))); AddHitObject(barLine); RemoveHitObject(barLine); @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { StartTime = 200, Major = false - }), null)); + }))); AddHitObject(barLine); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs index 54450e27db..c389a05566 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Duration = 500, IsStrong = false, TickRate = 2 - }), null)); + }))); AddHitObject(drumRoll); RemoveHitObject(drumRoll); @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Duration = 400, IsStrong = true, TickRate = 16 - }), null)); + }))); AddHitObject(drumRoll); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs index 52fd440857..c2f251fcb6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Type = HitType.Rim, IsStrong = false, StartTime = 300 - }), null)); + }))); AddHitObject(hit); RemoveHitObject(hit); @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Type = HitType.Centre, IsStrong = true, StartTime = 500 - }), null)); + }))); AddHitObject(hit); } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d0fa7eb22f..ba2b8423d0 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -206,12 +206,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Applies a hit object to be represented by this . /// - /// This overload is semi-deprecated. Use either or . + [Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")] public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry) { - if (lifetimeEntry != null && lifetimeEntry.HitObject != hitObject) - throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); - if (lifetimeEntry != null) Apply(lifetimeEntry); else diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index d55005363c..17d3cf01a4 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.UI lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject); dho.ParentHitObject = parent; - dho.Apply(hitObject, entry); + dho.Apply(entry); }); } From e90d791754776b5b5c51d42083145a665b9912c6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Apr 2021 09:14:19 +0300 Subject: [PATCH 126/400] Add base "classic" mod --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 17 +-------------- osu.Game/Rulesets/Mods/ModClassic.cs | 24 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModClassic.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 882f848190..77dea5b0dc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -16,22 +15,8 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModClassic : Mod, IApplicableToHitObject, IApplicableToDrawableHitObjects, IApplicableToDrawableRuleset + public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObjects, IApplicableToDrawableRuleset { - public override string Name => "Classic"; - - public override string Acronym => "CL"; - - public override double ScoreMultiplier => 1; - - public override IconUsage? Icon => FontAwesome.Solid.History; - - public override string Description => "Feeling nostalgic?"; - - public override bool Ranked => false; - - public override ModType Type => ModType.Conversion; - [SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")] public Bindable NoSliderHeadAccuracy { get; } = new BindableBool(true); diff --git a/osu.Game/Rulesets/Mods/ModClassic.cs b/osu.Game/Rulesets/Mods/ModClassic.cs new file mode 100644 index 0000000000..f1207ec188 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModClassic.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.Sprites; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModClassic : Mod + { + public override string Name => "Classic"; + + public override string Acronym => "CL"; + + public override double ScoreMultiplier => 1; + + public override IconUsage? Icon => FontAwesome.Solid.History; + + public override string Description => "Feeling nostalgic?"; + + public override bool Ranked => false; + + public override ModType Type => ModType.Conversion; + } +} From e3398d8f1fdfbbc57b6a3ae38e4ec9648f8505f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Apr 2021 09:14:31 +0300 Subject: [PATCH 127/400] Implement "classic" mod for all other legacy rulesets Currently empty, automatically handled in game to not be selectable (see `Mod.HasImplementation`) --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 1 + osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs | 11 +++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs | 11 +++++++++++ osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 11 +++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + 6 files changed, 36 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs create mode 100644 osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index f4ddbd3021..ab877c21c1 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -114,6 +114,7 @@ namespace osu.Game.Rulesets.Catch return new Mod[] { new CatchModDifficultyAdjust(), + new CatchModClassic(), }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs b/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs new file mode 100644 index 0000000000..9624e84018 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModClassic.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. + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModClassic : ModClassic + { + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 88b63606b9..b3889bc7d3 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -239,6 +239,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModDualStages(), new ManiaModMirror(), new ManiaModDifficultyAdjust(), + new ManiaModClassic(), new ManiaModInvert(), new ManiaModConstantSpeed() }; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs new file mode 100644 index 0000000000..073dda9de8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.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. + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModClassic : ModClassic + { + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs new file mode 100644 index 0000000000..5a4d18be98 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.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. + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModClassic : ModClassic + { + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 56f58f404b..f4e158ec32 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -135,6 +135,7 @@ namespace osu.Game.Rulesets.Taiko { new TaikoModRandom(), new TaikoModDifficultyAdjust(), + new TaikoModClassic(), }; case ModType.Automation: From 1a715b2926de6c3a9e3322474c4fe1f20ec11a5e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Apr 2021 09:16:28 +0300 Subject: [PATCH 128/400] Append "classic" mod to legacy scores --- osu.Game/Scoring/ScoreInfo.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 222f69b025..bf131658ea 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -65,14 +65,19 @@ namespace osu.Game.Scoring { get { - if (mods != null) - return mods; - - if (localAPIMods == null) - return Array.Empty(); + Mod[] scoreMods = Array.Empty(); var rulesetInstance = Ruleset.CreateInstance(); - return apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + + if (mods != null) + scoreMods = mods; + else if (localAPIMods != null) + scoreMods = apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + + if (IsLegacyScore) + scoreMods = scoreMods.Append(rulesetInstance.GetAllMods().OfType().Single()).ToArray(); + + return scoreMods; } set { From eb20865c02898b77c2ad583e80c5108ae662d177 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 15:55:13 +0900 Subject: [PATCH 129/400] Show tablet preview with physical tablet counter-rotated for supplied user area selection Closes https://github.com/ppy/osu/issues/12399. Rotation animation is intentionally delayed slightly to give a better sense of what is going on (or maybe just look cool). --- .../Settings/Sections/Input/TabletAreaSelection.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index f61742093c..b670e3558c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -129,6 +129,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input rotation.BindTo(handler.Rotation); rotation.BindValueChanged(val => { + tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) .OnComplete(_ => checkBounds()); // required as we are using SSDQ. }); @@ -183,8 +184,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (!(tablet.Value?.Size is Vector2 size)) return; - float fitX = size.X / (DrawWidth - Padding.Left - Padding.Right); - float fitY = size.Y / DrawHeight; + float maxDimension = size.LengthFast; + + float fitX = maxDimension / (DrawWidth - Padding.Left - Padding.Right); + float fitY = maxDimension / DrawHeight; float adjust = MathF.Max(fitX, fitY); From ab2a8b5c898b2f212c670ee6a3f9dd89e8fd1b53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 16:12:09 +0900 Subject: [PATCH 130/400] Fix initial rotation not being set --- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index b670e3558c..412889d210 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) .OnComplete(_ => checkBounds()); // required as we are using SSDQ. - }); + }, true); tablet.BindTo(handler.Tablet); tablet.BindValueChanged(_ => Scheduler.AddOnce(updateTabletDetails)); From fb848f7544435470f330aec600b2fc14e5313936 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 16:33:14 +0900 Subject: [PATCH 131/400] Rename to MasterGameplayClock --- .../Screens/Play/MasterGameplayClockContainer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index affe24069d..fcbc6fae15 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play private FramedOffsetClock userOffsetClock; private FramedOffsetClock platformOffsetClock; - private LocalGameplayClock localGameplayClock; + private MasterGameplayClock masterGameplayClock; private Bindable userAudioOffset; private double startOffset; @@ -157,7 +157,7 @@ namespace osu.Game.Screens.Play // the final usable gameplay clock with user-set offsets applied. userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); - return localGameplayClock = new LocalGameplayClock(userOffsetClock); + return masterGameplayClock = new MasterGameplayClock(userOffsetClock); } /// @@ -180,8 +180,8 @@ namespace osu.Game.Screens.Play Track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); Track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); - localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); + masterGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); + masterGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); speedAdjustmentsApplied = true; } @@ -194,8 +194,8 @@ namespace osu.Game.Screens.Play Track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); Track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); - localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); + masterGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); + masterGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); speedAdjustmentsApplied = false; } @@ -218,13 +218,13 @@ namespace osu.Game.Screens.Play } } - private class LocalGameplayClock : GameplayClock + private class MasterGameplayClock : GameplayClock { public readonly List> MutableNonGameplayAdjustments = new List>(); public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; - public LocalGameplayClock(FramedOffsetClock underlyingClock) + public MasterGameplayClock(FramedOffsetClock underlyingClock) : base(underlyingClock) { } From deeb9e3765294cdaa656a664b1eee0f31a73f8f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 17:27:00 +0900 Subject: [PATCH 132/400] 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 ed2b27e1c7..3324af7c51 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 509cb3ddad..fcd1ed6987 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 4b67bd78a1..34810a3106 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From bbf2ec369b748dcb424f370178ec5402a5c46493 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 17:13:59 +0900 Subject: [PATCH 133/400] Remove SkinReloadableDrawable inheritance from DHO --- .../Objects/Drawables/DrawableHitObject.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ba2b8423d0..1369623a62 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; @@ -25,7 +26,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { [Cached(typeof(DrawableHitObject))] - public abstract class DrawableHitObject : SkinReloadableDrawable + public abstract class DrawableHitObject : PoolableDrawable { /// /// Invoked after this 's applied has had its defaults applied. @@ -178,12 +179,15 @@ namespace osu.Game.Rulesets.Objects.Drawables } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, ISkinSource skinSource) { config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); // Explicit non-virtual function call. base.AddInternal(Samples = new PausableSkinnableSound()); + + CurrentSkin = skinSource; + CurrentSkin.SourceChanged += onSkinSourceChanged; } protected override void LoadAsyncComplete() @@ -536,17 +540,19 @@ namespace osu.Game.Rulesets.Objects.Drawables #endregion - protected sealed override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); + #region Skinning + protected ISkinSource CurrentSkin { get; private set; } + + private void onSkinSourceChanged() => Scheduler.AddOnce(() => + { UpdateComboColour(); - ApplySkin(skin, allowFallback); + ApplySkin(CurrentSkin, true); if (IsLoaded) updateState(State.Value, true); - } + }); protected void UpdateComboColour() { @@ -616,6 +622,8 @@ namespace osu.Game.Rulesets.Objects.Drawables Samples.Stop(); } + #endregion + protected override void Update() { base.Update(); @@ -811,6 +819,8 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject != null) HitObject.DefaultsApplied -= onDefaultsApplied; + + CurrentSkin.SourceChanged -= onSkinSourceChanged; } } From b877a2973739c4cb8a32393bbb021392f536be82 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 17:55:01 +0900 Subject: [PATCH 134/400] Factor out pooling and lifetime management logic of DHO to a base class --- .../Objects/Drawables/DrawableHitObject.cs | 116 +++-------------- .../Objects/Pooling/DrawableObject.cs | 121 ++++++++++++++++++ 2 files changed, 141 insertions(+), 96 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1369623a62..312ed93e45 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,7 +11,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; @@ -20,13 +19,14 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; +using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.UI; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { [Cached(typeof(DrawableHitObject))] - public abstract class DrawableHitObject : PoolableDrawable + public abstract class DrawableHitObject : DrawableObject { /// /// Invoked after this 's applied has had its defaults applied. @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The currently represented by this . /// - public HitObject HitObject => lifetimeEntry?.HitObject; + public HitObject HitObject => Entry?.HitObject; /// /// The parenting , if any. @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The scoring result of this . /// - public JudgementResult Result => lifetimeEntry?.Result; + public JudgementResult Result => Entry?.Result; /// /// The relative X position of this hit object for sample playback balance adjustment. @@ -126,8 +126,6 @@ namespace osu.Game.Rulesets.Objects.Drawables private readonly Bindable userPositionalHitSounds = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); - public override bool RemoveWhenNotAlive => false; - public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; public override bool IsPresent => base.IsPresent || (State.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart); @@ -142,18 +140,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public IBindable State => state; - /// - /// Whether a is currently applied. - /// - private bool hasEntryApplied; - - /// - /// The controlling the lifetime of the currently-attached . - /// - /// Even if it is not null, it may not be fully applied until loaded ( is false). - [CanBeNull] - private HitObjectLifetimeEntry lifetimeEntry; - [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } @@ -167,15 +153,13 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// /// The to be initially applied to this . - /// If null, a hitobject is expected to be later applied via (or automatically via pooling). + /// If null, a hitobject is expected to be later applied via (or automatically via pooling). /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) + : base(initialHitObject != null ? new SyntheticHitObjectEntry(initialHitObject) : null) { - if (initialHitObject != null) - { - lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject); + if (Entry != null) ensureEntryHasResult(); - } } [BackgroundDependencyLoader] @@ -190,14 +174,6 @@ namespace osu.Game.Rulesets.Objects.Drawables CurrentSkin.SourceChanged += onSkinSourceChanged; } - protected override void LoadAsyncComplete() - { - base.LoadAsyncComplete(); - - if (lifetimeEntry != null && !hasEntryApplied) - Apply(lifetimeEntry); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -231,22 +207,15 @@ namespace osu.Game.Rulesets.Objects.Drawables Apply(new SyntheticHitObjectEntry(hitObject)); } - /// - /// Applies a new to be represented by this . - /// - public void Apply([NotNull] HitObjectLifetimeEntry newEntry) + protected sealed override void OnApply(HitObjectLifetimeEntry entry) { - free(); - - lifetimeEntry = newEntry; - // LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset. // We override this with DHO's InitialLifetimeOffset for a non-pooled DHO. - if (newEntry is SyntheticHitObjectEntry) - lifetimeEntry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + if (entry is SyntheticHitObjectEntry) + entry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; - LifetimeStart = lifetimeEntry.LifetimeStart; - LifetimeEnd = lifetimeEntry.LifetimeEnd; + LifetimeStart = entry.LifetimeStart; + LifetimeEnd = entry.LifetimeEnd; ensureEntryHasResult(); @@ -297,17 +266,10 @@ namespace osu.Game.Rulesets.Objects.Drawables else updateState(ArmedState.Idle, true); } - - hasEntryApplied = true; } - /// - /// Removes the currently applied - /// - private void free() + protected sealed override void OnFree(HitObjectLifetimeEntry entry) { - if (!hasEntryApplied) return; - StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); if (HitObject is IHasComboInformation combo) comboIndexBindable.UnbindFrom(combo.ComboIndexBindable); @@ -339,22 +301,8 @@ namespace osu.Game.Rulesets.Objects.Drawables OnFree(); ParentHitObject = null; - lifetimeEntry = null; clearExistingStateTransforms(); - - hasEntryApplied = false; - } - - protected sealed override void FreeAfterUse() - { - base.FreeAfterUse(); - - // Freeing while not in a pool would cause the DHO to not be usable elsewhere in the hierarchy without being re-applied. - if (!IsInPool) - return; - - free(); } /// @@ -402,8 +350,8 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onDefaultsApplied(HitObject hitObject) { - Debug.Assert(lifetimeEntry != null); - Apply(lifetimeEntry); + Debug.Assert(Entry != null); + Apply(Entry); DefaultsApplied?.Invoke(this); } @@ -486,7 +434,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Apply (generally fade-in) transforms leading into the start time. - /// The local drawable hierarchy is recursively delayed to for convenience. + /// The local drawable hierarchy is recursively delayed to for convenience. /// /// By default this will fade in the object from zero with no duration. /// @@ -661,30 +609,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action); - public override double LifetimeStart - { - get => base.LifetimeStart; - set => setLifetime(value, LifetimeEnd); - } - - public override double LifetimeEnd - { - get => base.LifetimeEnd; - set => setLifetime(LifetimeStart, value); - } - - private void setLifetime(double lifetimeStart, double lifetimeEnd) - { - base.LifetimeStart = lifetimeStart; - base.LifetimeEnd = lifetimeEnd; - - if (lifetimeEntry != null) - { - lifetimeEntry.LifetimeStart = lifetimeStart; - lifetimeEntry.LifetimeEnd = lifetimeEnd; - } - } - /// /// A safe offset prior to the start time of at which this may begin displaying contents. /// By default, s are assumed to display their contents within 10 seconds prior to the start time of . @@ -692,7 +616,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). + /// A more accurate should be set for further optimisation (in , for example). /// /// Only has an effect if this is not being pooled. /// For pooled s, use instead. @@ -808,9 +732,9 @@ namespace osu.Game.Rulesets.Objects.Drawables private void ensureEntryHasResult() { - Debug.Assert(lifetimeEntry != null); - lifetimeEntry.Result ??= CreateResult(HitObject.CreateJudgement()) - ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + Debug.Assert(Entry != null); + Entry.Result ??= CreateResult(HitObject.CreateJudgement()) + ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs new file mode 100644 index 0000000000..b29e6a6c3c --- /dev/null +++ b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs @@ -0,0 +1,121 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Diagnostics; +using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Pooling; + +namespace osu.Game.Rulesets.Objects.Pooling +{ + /// + /// A that is controlled by to implement drawable pooling and replay rewinding. + /// + /// The type storing state and controlling this drawable. + public abstract class DrawableObject : PoolableDrawable where TEntry : LifetimeEntry + { + /// + /// The entry holding essential state of this . + /// + protected TEntry? Entry { get; private set; } + + /// + /// Whether is applied to this . + /// When an initial entry is specified in the constructor, is set but not applied until loading is completed. + /// + protected bool HasEntryApplied { get; private set; } + + public override double LifetimeStart + { + get => base.LifetimeStart; + set => setLifetime(value, LifetimeEnd); + } + + public override double LifetimeEnd + { + get => base.LifetimeEnd; + set => setLifetime(LifetimeStart, value); + } + + public override bool RemoveWhenNotAlive => false; + public override bool RemoveCompletedTransforms => false; + + protected DrawableObject(TEntry? initialEntry = null) + { + Entry = initialEntry; + } + + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); + + if (Entry != null && !HasEntryApplied) + Apply(Entry); + } + + /// + /// Applies a new entry to be represented by this drawable. + /// If there is an existing entry applied, the entry will be replaced. + /// + public void Apply(TEntry entry) + { + freeIfInUse(); + + setLifetime(entry.LifetimeStart, entry.LifetimeEnd); + Entry = entry; + + OnApply(entry); + + HasEntryApplied = true; + } + + protected sealed override void FreeAfterUse() + { + base.FreeAfterUse(); + + if (IsInPool) + freeIfInUse(); + } + + /// + /// Invoked to apply a new entry to this drawable. + /// + protected virtual void OnApply(TEntry entry) + { + } + + /// + /// Invoked to revert application of the entry to this drawable. + /// + protected virtual void OnFree(TEntry entry) + { + } + + private void setLifetime(double start, double end) + { + base.LifetimeStart = start; + base.LifetimeEnd = end; + + if (Entry != null) + { + Entry.LifetimeStart = start; + Entry.LifetimeEnd = end; + } + } + + private void freeIfInUse() + { + if (!HasEntryApplied) return; + + Debug.Assert(Entry != null); + + OnFree(Entry); + + Entry = null; + setLifetime(double.MaxValue, double.MaxValue); + + HasEntryApplied = false; + } + } +} From c6c91cd9a5ce76554b277b0e7520e0aeb4312cef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 18:05:26 +0900 Subject: [PATCH 135/400] Refactor `WaveformOpacityMenuItem` to not receive whole config --- osu.Game/Screens/Edit/Editor.cs | 2 +- .../{WaveformOpacityMenu.cs => WaveformOpacityMenuItem.cs} | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) rename osu.Game/Screens/Edit/{WaveformOpacityMenu.cs => WaveformOpacityMenuItem.cs} (85%) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index fffea65456..360fbb36db 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -226,7 +226,7 @@ namespace osu.Game.Screens.Edit { Items = new[] { - new WaveformOpacityMenu(config) + new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), } } } diff --git a/osu.Game/Screens/Edit/WaveformOpacityMenu.cs b/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs similarity index 85% rename from osu.Game/Screens/Edit/WaveformOpacityMenu.cs rename to osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs index 5d209ae141..053c2fa4b0 100644 --- a/osu.Game/Screens/Edit/WaveformOpacityMenu.cs +++ b/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs @@ -4,18 +4,17 @@ using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Edit { - internal class WaveformOpacityMenu : MenuItem + internal class WaveformOpacityMenuItem : MenuItem { private readonly Bindable waveformOpacity; private readonly Dictionary menuItemLookup = new Dictionary(); - public WaveformOpacityMenu(OsuConfigManager config) + public WaveformOpacityMenuItem(Bindable waveformOpacity) : base("Waveform opacity") { Items = new[] @@ -26,7 +25,7 @@ namespace osu.Game.Screens.Edit createMenuItem(1f), }; - waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); + this.waveformOpacity = waveformOpacity; waveformOpacity.BindValueChanged(opacity => { foreach (var kvp in menuItemLookup) From 9d8f0c854d1bce0763c1e7e4113321a8e0646944 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 18:05:40 +0900 Subject: [PATCH 136/400] Setup configuration item for editor hit animations --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Screens/Edit/Editor.cs | 3 ++- .../Screens/Edit/HitAnimationsMenuItem.cs | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/HitAnimationsMenuItem.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index f9b1c9618b..09412b1f1b 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -143,6 +143,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f); + SetDefault(OsuSetting.EditorHitAnimations, false); } public OsuConfigManager(Storage storage) @@ -266,6 +267,7 @@ namespace osu.Game.Configuration GameplayDisableWinKey, SeasonalBackgroundMode, EditorWaveformOpacity, + EditorHitAnimations, DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 360fbb36db..da0e9ebbaf 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -224,9 +224,10 @@ namespace osu.Game.Screens.Edit }, new MenuItem("View") { - Items = new[] + Items = new MenuItem[] { new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), + new HitAnimationsMenuItem(config.GetBindable(OsuSetting.EditorHitAnimations)) } } } diff --git a/osu.Game/Screens/Edit/HitAnimationsMenuItem.cs b/osu.Game/Screens/Edit/HitAnimationsMenuItem.cs new file mode 100644 index 0000000000..fb7ab39f7a --- /dev/null +++ b/osu.Game/Screens/Edit/HitAnimationsMenuItem.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 JetBrains.Annotations; +using osu.Framework.Bindables; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit +{ + internal class HitAnimationsMenuItem : ToggleMenuItem + { + [UsedImplicitly] + private readonly Bindable hitAnimations; + + public HitAnimationsMenuItem(Bindable hitAnimations) + : base("Hit animations") + { + State.BindTo(this.hitAnimations = hitAnimations); + } + } +} From 47a4a07024b6557d475b92f1356ee15c0828376c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 19:15:10 +0900 Subject: [PATCH 137/400] Split out animation triggering of `MainCirclePiece` to be interface driven --- .../Objects/Drawables/DrawableHitCircle.cs | 2 ++ .../Skinning/Default/IMainCirclePiece.cs | 17 +++++++++++++++++ .../Skinning/Default/MainCirclePiece.cs | 7 ++----- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 8 +++----- 4 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index df77ec2693..fb6c110b3c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -182,6 +182,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // todo: temporary / arbitrary, used for lifetime optimisation. this.Delay(800).FadeOut(); + (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); + switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.cs new file mode 100644 index 0000000000..17a1e29094 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.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 osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public interface IMainCirclePiece + { + /// + /// Begins animating this . + /// + /// The of the related . + void Animate(ArmedState state); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs index 46aeadc59b..b46baa00ba 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs @@ -13,7 +13,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public class MainCirclePiece : CompositeDrawable + public class MainCirclePiece : CompositeDrawable, IMainCirclePiece { private readonly CirclePiece circle; private readonly RingPiece ring; @@ -67,12 +67,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }, true); indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true); - - drawableObject.ApplyCustomUpdateState += updateState; - updateState(drawableObject, drawableObject.State.Value); } - private void updateState(DrawableHitObject drawableObject, ArmedState state) + public void Animate(ArmedState state) { using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) glow.FadeOut(400); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index 545e80a709..cf62165929 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -19,7 +20,7 @@ using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyMainCirclePiece : CompositeDrawable + public class LegacyMainCirclePiece : CompositeDrawable, IMainCirclePiece { private readonly string priorityLookup; private readonly bool hasNumber; @@ -138,12 +139,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); if (hasNumber) indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); - - drawableObject.ApplyCustomUpdateState += updateState; - updateState(drawableObject, drawableObject.State.Value); } - private void updateState(DrawableHitObject drawableObject, ArmedState state) + public void Animate(ArmedState state) { const double legacy_fade_duration = 240; From f2824a222a13f924df12f4479ccf3b8d68e5f6b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 19:41:15 +0900 Subject: [PATCH 138/400] Adjust existing fades to close match stable editor --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 5fdb79cbbd..dfb71d1d22 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit case DrawableHitCircle circle: // also handles slider heads circle.ApproachCircle - .FadeOutFromOne(editor_hit_object_fade_out_extension) + .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) .Expire(); break; } @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Edit hitObject.RemoveTransform(existing); - using (hitObject.BeginAbsoluteSequence(existing.StartTime)) + using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime)) hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); } } From 0f70469d1c33f392def6ae1070f51cdad941643c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 19:44:17 +0900 Subject: [PATCH 139/400] Only apply custom editor overrides if hit animations is disabled --- .../Edit/DrawableOsuEditRuleset.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index dfb71d1d22..b8d0637e90 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -27,8 +30,16 @@ namespace osu.Game.Rulesets.Osu.Edit private class OsuEditPlayfield : OsuPlayfield { + private Bindable hitAnimations; + protected override GameplayCursorContainer CreateCursor() => null; + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + hitAnimations = config.GetBindable(OsuSetting.EditorHitAnimations); + } + protected override void OnNewDrawableHitObject(DrawableHitObject d) { d.ApplyCustomUpdateState += updateState; @@ -42,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState(DrawableHitObject hitObject, ArmedState state) { - if (state == ArmedState.Idle) + if (state == ArmedState.Idle || hitAnimations.Value) return; // adjust the visuals of certain object types to make them stay on screen for longer than usual. @@ -60,6 +71,15 @@ namespace osu.Game.Rulesets.Osu.Edit circle.ApproachCircle .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) .Expire(); + + circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); + + var circlePieceDrawable = circle.CirclePiece.Drawable; + + // clear any explode animation logic. + circlePieceDrawable.ApplyTransformsAt(circle.HitStateUpdateTime, true); + circlePieceDrawable.ClearTransformsAfter(circle.HitStateUpdateTime, true); + break; } From de04caeace220426ed615efa6d3cabe4f76c88af Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 21 Apr 2021 13:53:08 +0200 Subject: [PATCH 140/400] Fixed race condition in StarRatingDisplay --- .../Ranking/Expanded/StarRatingDisplay.cs | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index a3e9336648..8367b1fc6d 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -28,9 +29,10 @@ namespace osu.Game.Screens.Ranking.Expanded private OsuColour colours { get; set; } private CircularContainer colorContainer; - private OsuTextFlowContainer textContainer; private CancellationTokenSource cancellationTokenSource; private IBindable bindableStarDifficulty; + private OsuSpriteText wholePartText; + private OsuSpriteText fractionPartText; private readonly StarDifficulty starDifficulty; private readonly BeatmapInfo beatmapInfo; @@ -66,24 +68,10 @@ namespace osu.Game.Screens.Ranking.Expanded colorContainer.Colour = backgroundColour; - textContainer.Text = string.Empty; + wholePartText.Text = $"{wholePart}"; + fractionPartText.Text = $"{separator}{fractionPart}"; - textContainer.With(t => - { - t.AddText($"{wholePart}", s => - { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 14); - s.UseFullGlyphHeight = false; - }); - - t.AddText($"{separator}{fractionPart}", s => - { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 7); - s.UseFullGlyphHeight = false; - }); - }); + } [BackgroundDependencyLoader] @@ -130,14 +118,28 @@ namespace osu.Game.Screens.Ranking.Expanded Icon = FontAwesome.Solid.Star, Colour = Color4.Black }, - textContainer = new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) + new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, TextAnchor = Anchor.BottomLeft, - }, + }.With(t => + { + t.AddText(wholePartText = new OsuSpriteText(), s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size:14); + s.UseFullGlyphHeight = false; + }); + t.AddText(fractionPartText = new OsuSpriteText(), s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 7); + s.UseFullGlyphHeight = false; + }); + }), } } }; From 9fba87f67ace6c74bb452d8b8720a78dfc97b82c Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 21 Apr 2021 13:53:25 +0200 Subject: [PATCH 141/400] Moved Info and Background into own container --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 8 ++- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 58 +++++++++++++------ 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 9c10c9751c..90bd4ceb88 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -135,15 +135,15 @@ namespace osu.Game.Tests.Visual.SongSelect private void selectBeatmap([CanBeNull] IBeatmap b) { - BeatmapInfoWedge.BufferedWedgeBackground backgroundBefore = null; + BeatmapInfoWedge.BeatmapInfoWedgeContainer containerBefore = null; AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () => { - backgroundBefore = infoWedge.Background; + containerBefore = infoWedge.Container; infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b); }); - AddUntilStep("wait for async load", () => infoWedge.Background != backgroundBefore); + AddUntilStep("wait for async load", () => infoWedge.Container != containerBefore); } private IBeatmap createTestBeatmap(RulesetInfo ruleset) @@ -196,6 +196,8 @@ namespace osu.Game.Tests.Visual.SongSelect public new BufferedWedgeBackground Background => base.Background; public new WedgeInfoText Info => base.Info; + + public new BeatmapInfoWedgeContainer Container => base.Container; } private class TestHitObject : ConvertHitObject, IHasPosition diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 9077c115d4..5bb7bbe9fd 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -41,8 +41,9 @@ namespace osu.Game.Screens.Select [Resolved] private IBindable ruleset { get; set; } - protected BufferedWedgeBackground Background; - protected WedgeInfoText Info; + protected BeatmapInfoWedgeContainer Container; + protected WedgeInfoText Info => Container.Info; + protected BufferedWedgeBackground Background => Container.Background; public BeatmapInfoWedge() { @@ -94,9 +95,9 @@ namespace osu.Game.Screens.Select } } - public override bool IsPresent => base.IsPresent || Background == null; // Visibility is updated in the LoadComponentAsync callback + public override bool IsPresent => base.IsPresent || Container == null; // Visibility is updated in the LoadComponentAsync callback - private BufferedWedgeBackground loadingInfo; + private BeatmapInfoWedgeContainer loadingInfo; private void updateDisplay() { @@ -108,13 +109,9 @@ namespace osu.Game.Screens.Select { State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; - Info?.FadeOut(250); - Info?.Expire(); - Info = null; - - Background?.FadeOut(250); - Background?.Expire(); - Background = null; + Container?.FadeOut(250); + Container?.Expire(); + Container = null; } if (beatmap == null) @@ -123,25 +120,50 @@ namespace osu.Game.Screens.Select return; } - LoadComponentAsync(loadingInfo = new BufferedWedgeBackground(beatmap) + LoadComponentAsync(loadingInfo = new BeatmapInfoWedgeContainer(beatmap, ruleset.Value) { Shear = -Shear, - Depth = Background?.Depth + 1 ?? 0 }, loaded => { // ensure we are the most recent loaded wedge. if (loaded != loadingInfo) return; removeOldInfo(); - Add(Background = loaded); - Add(Info = new WedgeInfoText(beatmap, ruleset.Value) - { - Shear = -Shear - }); + Add(Container = loaded); }); } } + public class BeatmapInfoWedgeContainer : Container + { + private readonly WorkingBeatmap beatmap; + private readonly RulesetInfo ruleset; + + internal BufferedWedgeBackground Background; + internal WedgeInfoText Info; + + public BeatmapInfoWedgeContainer(WorkingBeatmap beatmap, RulesetInfo ruleset) + { + this.beatmap = beatmap; + this.ruleset = ruleset; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + Background = new BufferedWedgeBackground(beatmap) + { + Depth = Background?.Depth + 1 ?? 0, + }, + Info = new WedgeInfoText(beatmap, ruleset), + }; + } + } + public class WedgeInfoText : Container { public FillFlowContainer MapperContainer { get; private set; } From d6928e91fd1046373476a3ba3a53d16ce55563b4 Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 21 Apr 2021 14:02:09 +0200 Subject: [PATCH 142/400] Removed BeatmapInfo in StarRatingDisplay --- .../Ranking/Expanded/StarRatingDisplay.cs | 45 +++++-------------- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 14 +++--- 2 files changed, 20 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 8367b1fc6d..ea4936bd82 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -29,13 +29,19 @@ namespace osu.Game.Screens.Ranking.Expanded private OsuColour colours { get; set; } private CircularContainer colorContainer; - private CancellationTokenSource cancellationTokenSource; - private IBindable bindableStarDifficulty; private OsuSpriteText wholePartText; private OsuSpriteText fractionPartText; + private StarDifficulty starDifficulty; - private readonly StarDifficulty starDifficulty; - private readonly BeatmapInfo beatmapInfo; + public StarDifficulty StarDifficulty + { + get => starDifficulty; + set + { + starDifficulty = value; + setDifficulty(starDifficulty); + } + } /// /// Creates a new using an already computed . @@ -46,15 +52,6 @@ namespace osu.Game.Screens.Ranking.Expanded this.starDifficulty = starDifficulty; } - /// - /// Creates a new using a to use a bindable for the difficulty. - /// - /// The to use to create a bindable for - public StarRatingDisplay(BeatmapInfo beatmapInfo) - { - this.beatmapInfo = beatmapInfo; - } - private void setDifficulty(StarDifficulty difficulty) { var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); @@ -71,21 +68,12 @@ namespace osu.Game.Screens.Ranking.Expanded wholePartText.Text = $"{wholePart}"; fractionPartText.Text = $"{separator}{fractionPart}"; - + } [BackgroundDependencyLoader] private void load(BeatmapDifficultyCache difficultyCache) { - if (beatmapInfo != null) - { - cancellationTokenSource?.Cancel(); - cancellationTokenSource = new CancellationTokenSource(); - - bindableStarDifficulty?.UnbindAll(); - bindableStarDifficulty = difficultyCache.GetBindableDifficulty(beatmapInfo, cancellationTokenSource.Token); - } - AutoSizeAxes = Axes.Both; InternalChildren = new Drawable[] @@ -144,16 +132,7 @@ namespace osu.Game.Screens.Ranking.Expanded } }; - if (bindableStarDifficulty != null) - bindableStarDifficulty.BindValueChanged(valueChanged => setDifficulty(valueChanged.NewValue ?? new StarDifficulty()), true); - else - setDifficulty(starDifficulty); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - cancellationTokenSource?.Cancel(); + setDifficulty(starDifficulty); } } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 5bb7bbe9fd..7803b190e6 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -178,7 +178,7 @@ namespace osu.Game.Screens.Select private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; private FillFlowContainer infoLabelContainer; - private Drawable starRatingDisplay; + private StarRatingDisplay starRatingDisplay; private Container bpmLabelContainer; private ModSettingChangeTracker settingChangeTracker; private CancellationTokenSource cancellationTokenSource; @@ -246,9 +246,9 @@ namespace osu.Game.Screens.Select Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, AutoSizeAxes = Axes.Both, Shear = wedged_container_shear, - Children = new[] + Children = new Drawable[] { - starRatingDisplay = new StarRatingDisplay(beatmapInfo) + starRatingDisplay = new StarRatingDisplay(starDifficulty.Value ?? new StarDifficulty()) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -311,19 +311,21 @@ namespace osu.Game.Screens.Select titleBinding.BindValueChanged(_ => setMetadata(metadata.Source)); artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true); - starDifficulty.BindValueChanged(_ => setStarRatingDisplayVisibility(), true); + starDifficulty.BindValueChanged(updateStarRatingDisplay, true); // no difficulty means it can't have a status to show if (beatmapInfo.Version == null) StatusPill.Hide(); } - private void setStarRatingDisplayVisibility() + private void updateStarRatingDisplay(ValueChangedEvent valueChanged) { - if (starDifficulty.Value.HasValue && starDifficulty.Value.Value.Stars > 0) + if (valueChanged.NewValue.HasValue && valueChanged.NewValue.Value.Stars > 0) starRatingDisplay.Show(); else starRatingDisplay.Hide(); + + starRatingDisplay.StarDifficulty = valueChanged.NewValue ?? new StarDifficulty(); } private InfoLabel[] getRulesetInfoLabels() From 0dfd0bb59d731a46419f4840883ff433b88ce5d1 Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 21 Apr 2021 14:16:16 +0200 Subject: [PATCH 143/400] Refactored background of BeatmapInfoWedge --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 2 - osu.Game/Screens/Select/BeatmapInfoWedge.cs | 57 +--------------- .../Select/BeatmapInfoWedgeBackground.cs | 66 +++++++++++++++++++ 3 files changed, 68 insertions(+), 57 deletions(-) create mode 100644 osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 90bd4ceb88..688cc9a035 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -193,8 +193,6 @@ namespace osu.Game.Tests.Visual.SongSelect private class TestBeatmapInfoWedge : BeatmapInfoWedge { - public new BufferedWedgeBackground Background => base.Background; - public new WedgeInfoText Info => base.Info; public new BeatmapInfoWedgeContainer Container => base.Container; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 7803b190e6..b499423412 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -11,7 +11,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -43,7 +42,6 @@ namespace osu.Game.Screens.Select protected BeatmapInfoWedgeContainer Container; protected WedgeInfoText Info => Container.Info; - protected BufferedWedgeBackground Background => Container.Background; public BeatmapInfoWedge() { @@ -123,6 +121,7 @@ namespace osu.Game.Screens.Select LoadComponentAsync(loadingInfo = new BeatmapInfoWedgeContainer(beatmap, ruleset.Value) { Shear = -Shear, + Depth = Container?.Depth + 1 ?? 0, }, loaded => { // ensure we are the most recent loaded wedge. @@ -139,7 +138,6 @@ namespace osu.Game.Screens.Select private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; - internal BufferedWedgeBackground Background; internal WedgeInfoText Info; public BeatmapInfoWedgeContainer(WorkingBeatmap beatmap, RulesetInfo ruleset) @@ -155,10 +153,7 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { - Background = new BufferedWedgeBackground(beatmap) - { - Depth = Background?.Depth + 1 ?? 0, - }, + new BeatmapInfoWedgeBackground(beatmap), Info = new WedgeInfoText(beatmap, ruleset), }; } @@ -572,53 +567,5 @@ namespace osu.Game.Screens.Select settingChangeTracker?.Dispose(); } } - - public class BufferedWedgeBackground : BufferedContainer - { - private readonly WorkingBeatmap beatmap; - - public BufferedWedgeBackground(WorkingBeatmap beatmap) - : base(pixelSnapping: true) - { - this.beatmap = beatmap; - } - - [BackgroundDependencyLoader] - private void load(LocalisationManager localisation) - { - CacheDrawnFrameBuffer = true; - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - // We will create the white-to-black gradient by modulating transparency and having - // a black backdrop. This results in an sRGB-space gradient and not linear space, - // transitioning from white to black more perceptually uniformly. - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - // We use a container, such that we can set the colour gradient to go across the - // vertices of the masked container instead of the vertices of the (larger) sprite. - new Container - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0.3f)), - Children = new[] - { - // Zoomed-in and cropped beatmap background - new BeatmapBackgroundSprite(beatmap) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - }, - }, - }, - }; - } - } } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs new file mode 100644 index 0000000000..566f49a799 --- /dev/null +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.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 osuTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Screens.Select +{ + internal class BeatmapInfoWedgeBackground : CompositeDrawable + { + private readonly WorkingBeatmap beatmap; + + public BeatmapInfoWedgeBackground(WorkingBeatmap beatmap) + { + this.beatmap = beatmap; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new BufferedContainer(pixelSnapping: true) + { + CacheDrawnFrameBuffer = true, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + // We will create the white-to-black gradient by modulating transparency and having + // a black backdrop. This results in an sRGB-space gradient and not linear space, + // transitioning from white to black more perceptually uniformly. + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + // We use a container, such that we can set the colour gradient to go across the + // vertices of the masked container instead of the vertices of the (larger) sprite. + new Container + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0.3f)), + Children = new[] + { + // Zoomed-in and cropped beatmap background + new BeatmapBackgroundSprite(beatmap) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, + }, + }, + } + }; + } + } +} From 56a69ed95682e9096f2b8e4b518346b7a66b9308 Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 21 Apr 2021 15:53:28 +0200 Subject: [PATCH 144/400] Codestyle fixes --- osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index ea4936bd82..bfc336a677 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Globalization; -using System.Threading; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -67,8 +65,6 @@ namespace osu.Game.Screens.Ranking.Expanded wholePartText.Text = $"{wholePart}"; fractionPartText.Text = $"{separator}{fractionPart}"; - - } [BackgroundDependencyLoader] @@ -118,7 +114,7 @@ namespace osu.Game.Screens.Ranking.Expanded t.AddText(wholePartText = new OsuSpriteText(), s => { s.Colour = Color4.Black; - s.Font = s.Font.With(size:14); + s.Font = s.Font.With(size: 14); s.UseFullGlyphHeight = false; }); t.AddText(fractionPartText = new OsuSpriteText(), s => From 60b702549dd2bfc42f21a73831396edf9c7dedac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 14:20:49 +0900 Subject: [PATCH 145/400] 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 3324af7c51..7b97103851 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 fcd1ed6987..b27e00f59d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 34810a3106..d07e16ea56 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 21f34be19fe58a3cfaca1f2182dc1054aed0aca4 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 22 Apr 2021 14:42:10 +0900 Subject: [PATCH 146/400] Add support for per-ruleset sample playback when switching rulesets (via toolbar) --- .../Overlays/Toolbar/ToolbarRulesetSelector.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 905d5b44c6..eb235632e8 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.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.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; @@ -13,6 +14,8 @@ using osu.Framework.Input.Events; using osuTK.Input; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; namespace osu.Game.Overlays.Toolbar { @@ -20,6 +23,8 @@ namespace osu.Game.Overlays.Toolbar { protected Drawable ModeButtonLine { get; private set; } + private readonly Dictionary selectionSamples = new Dictionary(); + public ToolbarRulesetSelector() { RelativeSizeAxes = Axes.Y; @@ -27,7 +32,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { AddRangeInternal(new[] { @@ -54,6 +59,9 @@ namespace osu.Game.Overlays.Toolbar } } }); + + foreach (var ruleset in Rulesets.AvailableRulesets) + selectionSamples[ruleset.ShortName] = audio.Samples.Get($"UI/ruleset-select-{ruleset.ShortName}"); } protected override void LoadComplete() @@ -72,6 +80,10 @@ namespace osu.Game.Overlays.Toolbar if (SelectedTab != null) { ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint); + + if (hasInitialPosition) + selectionSamples[SelectedTab.Value.ShortName]?.Play(); + hasInitialPosition = true; } }); From ea3bb07924f6be8126dc8bc1894478db9f00ba82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 14:51:14 +0900 Subject: [PATCH 147/400] Add test that fails on incorrect system/info message ordering --- .../Online/TestSceneStandAloneChatDisplay.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 01e67b1681..165fff99dd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -83,6 +83,28 @@ namespace osu.Game.Tests.Visual.Online }; }); + [Test] + public void TestSystemMessageOrdering() + { + var standardMessage = new Message(messageIdSequence++) + { + Sender = admin, + Content = "I am a wang!" + }; + + var infoMessage1 = new InfoMessage($"the system is calling {messageIdSequence++}"); + var infoMessage2 = new InfoMessage($"the system is calling {messageIdSequence++}"); + + AddStep("message from admin", () => testChannel.AddNewMessages(standardMessage)); + AddStep("message from system", () => testChannel.AddNewMessages(infoMessage1)); + AddStep("message from system", () => testChannel.AddNewMessages(infoMessage2)); + + AddAssert("message order is correct", () => testChannel.Messages.Count == 3 + && testChannel.Messages[0] == standardMessage + && testChannel.Messages[1] == infoMessage1 + && testChannel.Messages[2] == infoMessage2); + } + [Test] public void TestManyMessages() { From 3befb49ea9e5ed6420c053ba4b07bd58f56fac75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 14:51:57 +0900 Subject: [PATCH 148/400] Fix system messages always being displayed above standard messages Closes https://github.com/ppy/osu/issues/12509. --- osu.Game/Online/Chat/InfoMessage.cs | 4 +--- osu.Game/Online/Chat/Message.cs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/InfoMessage.cs b/osu.Game/Online/Chat/InfoMessage.cs index 8dce188804..cea336aae2 100644 --- a/osu.Game/Online/Chat/InfoMessage.cs +++ b/osu.Game/Online/Chat/InfoMessage.cs @@ -8,10 +8,8 @@ namespace osu.Game.Online.Chat { public class InfoMessage : LocalMessage { - private static int infoID = -1; - public InfoMessage(string message) - : base(infoID--) + : base(null) { Timestamp = DateTimeOffset.Now; Content = message; diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 2e41038a59..30753b3920 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -59,7 +59,7 @@ namespace osu.Game.Online.Chat return Id.Value.CompareTo(other.Id.Value); } - public virtual bool Equals(Message other) => Id == other?.Id; + public virtual bool Equals(Message other) => Id.HasValue && Id == other?.Id; // ReSharper disable once ImpureMethodCallOnReadonlyValueField public override int GetHashCode() => Id.GetHashCode(); From b37c5a8749422ccdd93caeeea815aa5013f26353 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 14:59:57 +0900 Subject: [PATCH 149/400] Rollback hold note placement when length is zero --- .../Edit/Blueprints/HoldNotePlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index a13afdfffe..093a8da24f 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return; base.OnMouseUp(e); - EndPlacement(true); + EndPlacement(HitObject.Duration > 0); } private double originalStartTime; From cf1e3ea98873897de95984f79846ca7282a7581f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 15:41:21 +0900 Subject: [PATCH 150/400] Add failing test covering quick shift-rightclick deletion in placement mode --- .../Editing/TestSceneEditorSelection.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs index 99f31b0c2a..c9b6d2e376 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Tests.Beatmaps; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -156,9 +157,35 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestQuickDeleteRemovesObject() + public void TestQuickDeleteRemovesObjectInPlacement() { - var addedObject = new HitCircle { StartTime = 1000 }; + var addedObject = new HitCircle + { + StartTime = 0, + Position = OsuPlayfield.BASE_SIZE * 0.5f + }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("enter placement mode", () => InputManager.PressKey(Key.Number2)); + + moveMouseToObject(() => addedObject); + + AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + + AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0); + } + + [Test] + public void TestQuickDeleteRemovesObjectInSelection() + { + var addedObject = new HitCircle + { + StartTime = 0, + Position = OsuPlayfield.BASE_SIZE * 0.5f + }; AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); From 9a7bf8109ff7029ad76e2d60b389d660d062ec57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 15:27:08 +0900 Subject: [PATCH 151/400] Allow certain mouse input to pass through `PlacementBlueprints` to the selection logic --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 6c1cd01796..4ad8c815fe 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -128,8 +129,11 @@ namespace osu.Game.Rulesets.Edit case DoubleClickEvent _: return false; - case MouseButtonEvent _: - return true; + case MouseButtonEvent mouse: + // placement blueprints should generally block mouse from reaching underlying components (ie. performing clicks on interface buttons). + // for now, the one exception we want to allow is when using a non-main mouse button when shift is pressed, which is used to trigger object deletion + // while in placement mode. + return mouse.Button == MouseButton.Left || !mouse.ShiftPressed; default: return false; From 3e1002fbf36650b9a91a6a04df9d15c0d220f8f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 17:06:08 +0900 Subject: [PATCH 152/400] Improve osu!catch caught fruit placement algorithm --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 29 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index d045dcf16a..8edc5d0eb2 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -53,6 +53,11 @@ namespace osu.Game.Rulesets.Catch.UI /// public const double BASE_SPEED = 1.0; + /// + /// The amount by which caught fruit should be scaled down to fit on the plate. + /// + private const float caught_fruit_scale_adjust = 0.5f; + [NotNull] private readonly Container trailsTarget; @@ -240,7 +245,7 @@ namespace osu.Game.Rulesets.Catch.UI if (result.IsHit) { - var positionInStack = computePositionInStack(new Vector2(palpableObject.X - X, 0), palpableObject.DisplaySize.X / 2); + var positionInStack = computePositionInStack(new Vector2(palpableObject.X - X, 0), palpableObject.DisplaySize.X); if (CatchFruitOnPlate) placeCaughtObject(palpableObject, positionInStack); @@ -470,7 +475,7 @@ namespace osu.Game.Rulesets.Catch.UI caughtObject.CopyStateFrom(drawableObject); caughtObject.Anchor = Anchor.TopCentre; caughtObject.Position = position; - caughtObject.Scale /= 2; + caughtObject.Scale *= caught_fruit_scale_adjust; caughtObjectContainer.Add(caughtObject); @@ -480,19 +485,21 @@ namespace osu.Game.Rulesets.Catch.UI private Vector2 computePositionInStack(Vector2 position, float displayRadius) { - const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2; - const float allowance = 10; + // this is taken from osu-stable (lenience should be 10 * 10 at standard scale). + const float lenience_adjust = 10 / CatchHitObject.OBJECT_RADIUS; - while (caughtObjectContainer.Any(f => Vector2Extensions.Distance(f.Position, position) < (displayRadius + radius_div_2) / (allowance / 2))) + float adjustedRadius = displayRadius * lenience_adjust; + float checkDistance = MathF.Pow(adjustedRadius, 2); + + // offset fruit vertically to better place "above" the plate. + position.Y -= 5; + + while (caughtObjectContainer.Any(f => Vector2Extensions.DistanceSquared(f.Position, position) < checkDistance)) { - float diff = (displayRadius + radius_div_2) / allowance; - - position.X += (RNG.NextSingle() - 0.5f) * diff * 2; - position.Y -= RNG.NextSingle() * diff; + position.X += RNG.NextSingle(-adjustedRadius, adjustedRadius); + position.Y -= RNG.NextSingle(0, 5); } - position.X = Math.Clamp(position.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2); - return position; } From 84a713822306f5a6dd4897b58e3894f6b669190f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 16:56:23 +0900 Subject: [PATCH 153/400] Update tests to better support stack regeneration cases --- .../TestSceneCatcher.cs | 33 ++++++++++++++----- osu.Game.Rulesets.Catch/UI/Catcher.cs | 7 +++- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 48efd73222..f8e278a486 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.UI; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; @@ -170,16 +171,25 @@ namespace osu.Game.Rulesets.Catch.Tests } [Test] - public void TestCatcherStacking() + public void TestCatcherRandomStacking() + { + AddStep("catch more fruits", () => attemptCatch(() => new Fruit + { + X = (RNG.NextSingle() - 0.5f) * CatcherArea.CATCHER_SIZE + }, 50)); + } + + [Test] + public void TestCatcherStackingSameCaughtPosition() { AddStep("catch fruit", () => attemptCatch(new Fruit())); checkPlate(1); - AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9)); + AddStep("catch more fruits", () => attemptCatch(() => new Fruit(), 9)); checkPlate(10); AddAssert("caught objects are stacked", () => - catcher.CaughtObjects.All(obj => obj.Y <= 0) && - catcher.CaughtObjects.Any(obj => obj.Y == 0) && - catcher.CaughtObjects.Any(obj => obj.Y < -20)); + catcher.CaughtObjects.All(obj => obj.Y <= Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) && + catcher.CaughtObjects.Any(obj => obj.Y == Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) && + catcher.CaughtObjects.Any(obj => obj.Y < -25)); } [Test] @@ -189,11 +199,11 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet())); AddAssert("tiny droplet is exploded", () => catcher.CaughtObjects.Count() == 1 && droppedObjectContainer.Count == 1); AddUntilStep("wait explosion", () => !droppedObjectContainer.Any()); - AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9)); + AddStep("catch more fruits", () => attemptCatch(() => new Fruit(), 9)); AddStep("explode", () => catcher.Explode()); AddAssert("fruits are exploded", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10); AddUntilStep("wait explosion", () => !droppedObjectContainer.Any()); - AddStep("catch fruits", () => attemptCatch(new Fruit(), 10)); + AddStep("catch fruits", () => attemptCatch(() => new Fruit(), 10)); AddStep("drop", () => catcher.Drop()); AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10); } @@ -222,10 +232,15 @@ namespace osu.Game.Rulesets.Catch.Tests private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state); - private void attemptCatch(CatchHitObject hitObject, int count = 1) + private void attemptCatch(CatchHitObject hitObject) + { + attemptCatch(() => hitObject, 1); + } + + private void attemptCatch(Func hitObject, int count) { for (var i = 0; i < count; i++) - attemptCatch(hitObject, out _, out _); + attemptCatch(hitObject(), out _, out _); } private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 8edc5d0eb2..4fc9e32a73 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -53,6 +53,11 @@ namespace osu.Game.Rulesets.Catch.UI /// public const double BASE_SPEED = 1.0; + /// + /// The amount by which caught fruit should be offset from the plate surface to make them look visually "caught". + /// + public const float CAUGHT_FRUIT_VERTICAL_OFFSET = -5; + /// /// The amount by which caught fruit should be scaled down to fit on the plate. /// @@ -492,7 +497,7 @@ namespace osu.Game.Rulesets.Catch.UI float checkDistance = MathF.Pow(adjustedRadius, 2); // offset fruit vertically to better place "above" the plate. - position.Y -= 5; + position.Y += CAUGHT_FRUIT_VERTICAL_OFFSET; while (caughtObjectContainer.Any(f => Vector2Extensions.DistanceSquared(f.Position, position) < checkDistance)) { From dc2bc462b8c68b44b6e927ec84bbf87fc0586df3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 17:27:23 +0900 Subject: [PATCH 154/400] Expose internal catcher width calculation methods --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 4fc9e32a73..0d6a577d1e 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -212,13 +212,13 @@ namespace osu.Game.Rulesets.Catch.UI /// Calculates the width of the area used for attempting catches in gameplay. /// /// The scale of the catcher. - internal static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE; + public static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE; /// /// Calculates the width of the area used for attempting catches in gameplay. /// /// The beatmap difficulty. - internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty)); + public static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty)); /// /// Determine if this catcher can catch a in the current position. From 2203552e9e23528e79c63024d80186e4f5034bc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 17:32:24 +0900 Subject: [PATCH 155/400] Add stacking test logic to `TestSceneCatcherArea` for skinned testing --- .../TestSceneCatcher.cs | 3 +- .../TestSceneCatcherArea.cs | 36 +++++++++++++++---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index f8e278a486..517027a9fc 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Rulesets.Catch.Tests { @@ -175,7 +176,7 @@ namespace osu.Game.Rulesets.Catch.Tests { AddStep("catch more fruits", () => attemptCatch(() => new Fruit { - X = (RNG.NextSingle() - 0.5f) * CatcherArea.CATCHER_SIZE + X = (RNG.NextSingle() - 0.5f) * Catcher.CalculateCatchWidth(Vector2.One) }, 50)); } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 1cbfa6338e..3e4ee5ffa9 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -8,6 +8,8 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; @@ -31,12 +33,32 @@ namespace osu.Game.Rulesets.Catch.Tests private float circleSize; + private ScheduledDelegate addManyFruit; + + private BeatmapDifficulty beatmapDifficulty; + public TestSceneCatcherArea() { AddSliderStep("circle size", 0, 8, 5, createCatcher); AddToggleStep("hyper dash", t => this.ChildrenOfType().ForEach(area => area.ToggleHyperDash(t))); - AddStep("catch fruit", () => attemptCatch(new Fruit())); + AddStep("catch centered fruit", () => attemptCatch(new Fruit())); + AddStep("catch many random fruit", () => + { + int count = 50; + + addManyFruit?.Cancel(); + addManyFruit = Scheduler.AddDelayed(() => + { + attemptCatch(new Fruit + { + X = (RNG.NextSingle() - 0.5f) * Catcher.CalculateCatchWidth(beatmapDifficulty), + }); + + if (count-- == 0) + addManyFruit?.Cancel(); + }, 50, true); + }); AddStep("catch fruit last in combo", () => attemptCatch(new Fruit { LastInCombo = true })); AddStep("catch kiai fruit", () => attemptCatch(new TestSceneCatcher.TestKiaiFruit())); AddStep("miss last in combo", () => attemptCatch(new Fruit { X = 100, LastInCombo = true })); @@ -45,10 +67,7 @@ namespace osu.Game.Rulesets.Catch.Tests private void attemptCatch(Fruit fruit) { fruit.X = fruit.OriginalX + catcher.X; - fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty - { - CircleSize = circleSize - }); + fruit.ApplyDefaults(new ControlPointInfo(), beatmapDifficulty); foreach (var area in this.ChildrenOfType()) { @@ -71,6 +90,11 @@ namespace osu.Game.Rulesets.Catch.Tests { circleSize = size; + beatmapDifficulty = new BeatmapDifficulty + { + CircleSize = circleSize + }; + SetContents(() => { var droppedObjectContainer = new Container @@ -84,7 +108,7 @@ namespace osu.Game.Rulesets.Catch.Tests Children = new Drawable[] { droppedObjectContainer, - new TestCatcherArea(droppedObjectContainer, new BeatmapDifficulty { CircleSize = size }) + new TestCatcherArea(droppedObjectContainer, beatmapDifficulty) { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, From bdf07ad59a7cfc0abb796133001da52bd83f314b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 17:47:03 +0900 Subject: [PATCH 156/400] Limit catching towards the centre of the plate (to emulate actual gameplay) --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 3e4ee5ffa9..ad404e1f63 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Tests { attemptCatch(new Fruit { - X = (RNG.NextSingle() - 0.5f) * Catcher.CalculateCatchWidth(beatmapDifficulty), + X = (RNG.NextSingle() - 0.5f) * Catcher.CalculateCatchWidth(beatmapDifficulty) * 0.6f, }); if (count-- == 0) From 1884c18a2cc3d169012f89da64a16d65c7cd267f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 18:12:03 +0900 Subject: [PATCH 157/400] Ignore movement operations which have no offset --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index b1afbe0d61..f70e063ba9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -519,7 +519,8 @@ namespace osu.Game.Screens.Edit.Compose.Components // Apply the start time at the newly snapped-to position double offset = result.Time.Value - movementBlueprints.First().HitObject.StartTime; - Beatmap.PerformOnSelection(obj => obj.StartTime += offset); + if (offset != 0) + Beatmap.PerformOnSelection(obj => obj.StartTime += offset); } return true; From 8a6267580a207fc63c88ed3a962037eeff7ad162 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 18:44:14 +0900 Subject: [PATCH 158/400] Fix nullref --- osu.Game/Scoring/ScoreInfo.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index bf131658ea..a6faaf6379 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -65,9 +65,11 @@ namespace osu.Game.Scoring { get { - Mod[] scoreMods = Array.Empty(); + var rulesetInstance = Ruleset?.CreateInstance(); + if (rulesetInstance == null) + return mods ?? Array.Empty(); - var rulesetInstance = Ruleset.CreateInstance(); + Mod[] scoreMods = Array.Empty(); if (mods != null) scoreMods = mods; From a5364b224f083be5d55a31c95203f7a65069f3cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 18:47:04 +0900 Subject: [PATCH 159/400] Add simple key based time nudging support to editor --- .../Editing/TestSceneEditorSelection.cs | 22 ++++++++++ .../Input/Bindings/GlobalActionContainer.cs | 8 ++++ .../Timeline/TimelineBlueprintContainer.cs | 42 ++++++++++++++++++- 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs index 99f31b0c2a..c783ea1448 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs @@ -41,6 +41,28 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestNudgeSelection() + { + HitCircle[] addedObjects = null; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 200, Position = new Vector2(50) }, + new HitCircle { StartTime = 300, Position = new Vector2(100) }, + new HitCircle { StartTime = 400, Position = new Vector2(150) }, + })); + + AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); + + AddStep("nudge forwards", () => InputManager.Key(Key.K)); + AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 100); + + AddStep("nudge backwards", () => InputManager.Key(Key.J)); + AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100); + } + [Test] public void TestBasicSelect() { diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index e414e12dd1..6717de5658 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -71,6 +71,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode), new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode), + new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), + new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), }; public IEnumerable InGameKeyBindings => new[] @@ -251,5 +253,11 @@ namespace osu.Game.Input.Bindings [Description("Verify mode")] EditorVerifyMode, + + [Description("Nudge selection left")] + EditorNudgeLeft, + + [Description("Nudge selection right")] + EditorNudgeRight } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 7a3781a981..3555bc2800 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -12,9 +12,11 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -237,10 +239,48 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - internal class TimelineSelectionHandler : SelectionHandler + internal class TimelineSelectionHandler : SelectionHandler, IKeyBindingHandler { // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.EditorNudgeLeft: + nudgeSelection(-1); + return true; + + case GlobalAction.EditorNudgeRight: + nudgeSelection(1); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } + + /// + /// Nudge the current selection by the specified multiple of beat divisor lengths, + /// based on the timing at the first object in the selection. + /// + /// The direction and count of beat divisor lengths to adjust. + private void nudgeSelection(int amount) + { + var selected = EditorBeatmap.SelectedHitObjects; + + if (selected.Count == 0) + return; + + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime); + double adjustment = timingPoint.BeatLength / EditorBeatmap.BeatDivisor * amount; + + EditorBeatmap.PerformOnSelection(h => h.StartTime += adjustment); + } } private class TimelineDragBox : DragBox From 30e6ea4291dd95fb87108263df1f677e151d32f4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 18:59:57 +0900 Subject: [PATCH 160/400] Add failing test --- .../TestSceneHoldNoteInput.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 42ea12214f..668487f673 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -324,6 +324,33 @@ namespace osu.Game.Rulesets.Mania.Tests assertTailJudgement(HitResult.Ok); } + [Test] + public void TestZeroLength() + { + var beatmap = new Beatmap + { + HitObjects = + { + new HoldNote + { + StartTime = 1000, + Duration = 0, + Column = 0, + }, + }, + BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, + }; + + performTest(new List + { + new ManiaReplayFrame(beatmap.HitObjects[0].StartTime, ManiaAction.Key1), + new ManiaReplayFrame(beatmap.HitObjects[0].GetEndTime() + 1), + }, beatmap); + + AddAssert("hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject)) + .All(j => j.Type.IsHit())); + } + private void assertHeadJudgement(HitResult result) => AddAssert($"head judged as {result}", () => judgementResults.First(j => j.HitObject is Note).Type == result); From 4148d473e3d4c949b312abcbc915f2fd3e99e925 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 19:51:33 +0900 Subject: [PATCH 161/400] Fix hold note crashing with 0 length --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 828ee7b03e..02829d87bd 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // As the note is being held, adjust the size of the sizing container. This has two effects: // 1. The contained masking container will mask the body and ticks. // 2. The head note will move along with the new "head position" in the container. - if (Head.IsHit && releaseTime == null) + if (Head.IsHit && releaseTime == null && DrawHeight > 0) { // How far past the hit target this hold note is. Always a positive value. float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y); From d20a8694e47264a5d6363550d8f3582d70cb8d10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 19:55:06 +0900 Subject: [PATCH 162/400] 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 3324af7c51..5b779b803f 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 fcd1ed6987..da8163a971 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 34810a3106..dc7a57f905 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From b4f492ca4ce6e171bd3ff9cfd02d5a98140f0666 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 23:06:46 +0900 Subject: [PATCH 163/400] 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 7b97103851..140057f9cc 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 b27e00f59d..532dea7ace 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index d07e16ea56..edebefa70e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 0ee73b8e536a37d94d56fcfe4f1878716f5687bf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 23:22:44 +0900 Subject: [PATCH 164/400] Add failing test --- .../StatefulMultiplayerClientTest.cs | 26 +++++++++++++++++++ .../Multiplayer/TestMultiplayerClient.cs | 11 +++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index a2ad37cf4a..377a33b527 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -41,6 +41,32 @@ namespace osu.Game.Tests.NonVisual.Multiplayer checkPlayingUserCount(0); } + [Test] + public void TestPlayingUsersUpdatedOnJoin() + { + AddStep("leave room", () => Client.LeaveRoom()); + AddUntilStep("wait for room part", () => Client.Room == null); + + AddStep("create room initially in gameplay", () => + { + Room.RoomID.Value = null; + Client.RoomSetupAction = room => + { + room.State = MultiplayerRoomState.Playing; + room.Users.Add(new MultiplayerRoomUser(55) + { + User = new User { Id = 55 }, + State = MultiplayerUserState.Playing + }); + }; + + RoomManager.CreateRoom(Room); + }); + + AddUntilStep("wait for room join", () => Client.Room != null); + checkPlayingUserCount(1); + } + private void checkPlayingUserCount(int expectedCount) => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.CurrentMatchPlayingUserIds.Count == expectedCount); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index b5cd3dad02..de77a15da0 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -25,6 +25,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public override IBindable IsConnected => isConnected; private readonly Bindable isConnected = new Bindable(true); + public Action? RoomSetupAction; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -112,7 +114,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == roomId); - var user = new MultiplayerRoomUser(api.LocalUser.Value.Id) + var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value }; @@ -129,10 +131,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AllowedMods = apiRoom.Playlist.Last().AllowedMods.Select(m => new APIMod(m)).ToArray(), PlaylistItemId = apiRoom.Playlist.Last().ID }, - Users = { user }, - Host = user + Users = { localUser }, + Host = localUser }; + RoomSetupAction?.Invoke(room); + RoomSetupAction = null; + return Task.FromResult(room); } From f593d9e42c3efa144dc07df35f504bd15efc41c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 23:23:43 +0900 Subject: [PATCH 165/400] Fix playing users not being updated on room join --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 2ddc10db0f..c0706b082d 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -144,6 +144,8 @@ namespace osu.Game.Online.Multiplayer Room = joinedRoom; apiRoom = room; defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0; + foreach (var user in joinedRoom.Users) + updateUserPlayingState(user.UserID, user.State); }, cancellationSource.Token).ConfigureAwait(false); // Update room settings. From fbb9cb3f6f6d0a9d6fe0d89aa66ca26b545ec982 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Apr 2021 11:01:48 +0900 Subject: [PATCH 166/400] Fix broken merge resolution --- osu.Game/osu.Game.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d3d2de4557..75a3e45941 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,6 @@ - From bc0e1d8c37272acf7a75c048036c512d42777bcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Apr 2021 15:06:39 +0900 Subject: [PATCH 167/400] Remove dead newline --- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 4c46e24f1b..e4c41bed6d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -21,6 +21,5 @@ namespace osu.Game.Rulesets.Catch.Mods drawableRuleset.Origin = Anchor.Centre; drawableRuleset.Scale = new osuTK.Vector2(1, -1); } - } } From 713344ebadc752b16c83bab92331af3c5850be25 Mon Sep 17 00:00:00 2001 From: Denrage Date: Fri, 23 Apr 2021 10:31:49 +0200 Subject: [PATCH 168/400] Reorganize methods --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 214 ++++++++++---------- 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index b499423412..cb5a276a5d 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -323,6 +323,48 @@ namespace osu.Game.Screens.Select starRatingDisplay.StarDifficulty = valueChanged.NewValue ?? new StarDifficulty(); } + private void refreshModInformation(ValueChangedEvent> modsChangedEvent) + { + settingChangeTracker?.Dispose(); + settingChangeTracker = new ModSettingChangeTracker(modsChangedEvent.NewValue); + settingChangeTracker.SettingChanged += _ => refreshBPMLabel(modsChangedEvent.NewValue); + refreshBPMLabel(modsChangedEvent.NewValue); + } + + private void setMetadata(string source) + { + ArtistLabel.Text = artistBinding.Value; + TitleLabel.Text = string.IsNullOrEmpty(source) ? titleBinding.Value : source + " — " + titleBinding.Value; + } + + private void addInfoLabels() + { + if (beatmap.Beatmap?.HitObjects?.Any() != true) + return; + + infoLabelContainer.Children = new Drawable[] + { + new InfoLabel(new BeatmapStatistic + { + Name = "Length", + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), + Content = TimeSpan.FromMilliseconds(beatmap.BeatmapInfo.Length).ToString(@"m\:ss"), + }), + bpmLabelContainer = new Container + { + AutoSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(20, 0), + Children = getRulesetInfoLabels() + } + }; + + mods.BindValueChanged(refreshModInformation, true); + } + private InfoLabel[] getRulesetInfoLabels() { try @@ -377,48 +419,6 @@ namespace osu.Game.Screens.Select }); } - private void setMetadata(string source) - { - ArtistLabel.Text = artistBinding.Value; - TitleLabel.Text = string.IsNullOrEmpty(source) ? titleBinding.Value : source + " — " + titleBinding.Value; - } - - private void addInfoLabels() - { - if (beatmap.Beatmap?.HitObjects?.Any() != true) - return; - - infoLabelContainer.Children = new Drawable[] - { - new InfoLabel(new BeatmapStatistic - { - Name = "Length", - CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), - Content = TimeSpan.FromMilliseconds(beatmap.BeatmapInfo.Length).ToString(@"m\:ss"), - }), - bpmLabelContainer = new Container - { - AutoSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(20, 0), - Children = getRulesetInfoLabels() - } - }; - - mods.BindValueChanged(refreshModInformation, true); - } - - private void refreshModInformation(ValueChangedEvent> modsChangedEvent) - { - settingChangeTracker?.Dispose(); - settingChangeTracker = new ModSettingChangeTracker(modsChangedEvent.NewValue); - settingChangeTracker.SettingChanged += _ => refreshBPMLabel(modsChangedEvent.NewValue); - refreshBPMLabel(modsChangedEvent.NewValue); - } - private OsuSpriteText[] getMapper(BeatmapMetadata metadata) { if (string.IsNullOrEmpty(metadata.Author?.Username)) @@ -439,6 +439,71 @@ namespace osu.Game.Screens.Select }; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + settingChangeTracker?.Dispose(); + } + + public class InfoLabel : Container, IHasTooltip + { + public string TooltipText { get; } + + public InfoLabel(BeatmapStatistic statistic) + { + TooltipText = statistic.Name; + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20), + Children = new[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex(@"441288"), + Icon = FontAwesome.Solid.Square, + Rotation = 45, + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex(@"f7dd55"), + Icon = FontAwesome.Regular.Circle, + Size = new Vector2(0.8f) + }, + statistic.CreateIcon().With(i => + { + i.Anchor = Anchor.Centre; + i.Origin = Anchor.Centre; + i.RelativeSizeAxes = Axes.Both; + i.Colour = Color4Extensions.FromHex(@"f7dd55"); + i.Size = new Vector2(0.64f); + }), + } + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = new Color4(255, 221, 85, 255), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), + Margin = new MarginPadding { Left = 30 }, + Text = statistic.Content, + } + }; + } + } + private class DifficultyColourBar : Container { [Resolved] @@ -501,71 +566,6 @@ namespace osu.Game.Screens.Select cancellationTokenSource?.Cancel(); } } - - public class InfoLabel : Container, IHasTooltip - { - public string TooltipText { get; } - - public InfoLabel(BeatmapStatistic statistic) - { - TooltipText = statistic.Name; - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] - { - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20), - Children = new[] - { - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex(@"441288"), - Icon = FontAwesome.Solid.Square, - Rotation = 45, - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex(@"f7dd55"), - Icon = FontAwesome.Regular.Circle, - Size = new Vector2(0.8f) - }, - statistic.CreateIcon().With(i => - { - i.Anchor = Anchor.Centre; - i.Origin = Anchor.Centre; - i.RelativeSizeAxes = Axes.Both; - i.Colour = Color4Extensions.FromHex(@"f7dd55"); - i.Size = new Vector2(0.64f); - }), - } - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = new Color4(255, 221, 85, 255), - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), - Margin = new MarginPadding { Left = 30 }, - Text = statistic.Content, - } - }; - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - settingChangeTracker?.Dispose(); - } } } } From ae2fd2f2e1f4315d4c1b90a56d412fa7b54cc9f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Apr 2021 18:46:59 +0900 Subject: [PATCH 169/400] Ensure source is set on reset --- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 5cd17d92c4..6677116399 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -100,6 +100,7 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { + ChangeSource(SourceClock); Seek(0); // Manually stop the source in order to not affect the IsPaused state. From fdb5490e51f1edf5b09aed6ecaf428e7ff03e8a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Apr 2021 21:56:08 +0200 Subject: [PATCH 170/400] Attempt to explain source initialisation better --- osu.Game/Screens/Play/GameplayClockContainer.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6677116399..6d60c09521 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -63,8 +63,7 @@ namespace osu.Game.Screens.Play /// public virtual void Start() { - // Ensure that the source clock is set. - ChangeSource(SourceClock); + ensureSourceClockSet(); if (!AdjustableSource.IsRunning) { @@ -100,7 +99,7 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { - ChangeSource(SourceClock); + ensureSourceClockSet(); Seek(0); // Manually stop the source in order to not affect the IsPaused state. @@ -116,6 +115,15 @@ namespace osu.Game.Screens.Play /// The new source. protected void ChangeSource(IClock sourceClock) => AdjustableSource.ChangeSource(SourceClock = sourceClock); + /// + /// Ensures that the is set to . + /// This is usually done before a seek to avoid accidentally seeking only the adjustable source in decoupled mode, + /// but not the actual source clock. + /// That will pretty much only happen on the very first call of this method, as the source clock is passed in the constructor, + /// but it is not yet set on the adjustable source there. + /// + private void ensureSourceClockSet() => ChangeSource(SourceClock); + protected override void Update() { if (!IsPaused.Value) From 04958a043f8ce1f5e24a65c438f0ad4c4305e4f3 Mon Sep 17 00:00:00 2001 From: subfluid <76847113+subfluid@users.noreply.github.com> Date: Fri, 23 Apr 2021 20:54:06 -0700 Subject: [PATCH 171/400] Fix Spelling Error 'passses' line 20 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d6af2aeba..c539f9f4d8 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commo 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. +**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 passes 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: From 0ccdfeea57f3a983e1735ce1d3c0aca7b7959da2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Apr 2021 14:33:56 +0900 Subject: [PATCH 172/400] Fix code quality issues --- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index e4c41bed6d..63203dd57c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -1,9 +1,12 @@ -using osu.Framework.Graphics; +// 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.Sprites; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Rulesets.Catch.Mods { @@ -19,7 +22,8 @@ namespace osu.Game.Rulesets.Catch.Mods { drawableRuleset.Anchor = Anchor.Centre; drawableRuleset.Origin = Anchor.Centre; - drawableRuleset.Scale = new osuTK.Vector2(1, -1); + + drawableRuleset.Scale = new Vector2(1, -1); } } } From 2ae144be8e035801a3131b90c92542f3c075c03f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Apr 2021 14:38:00 +0900 Subject: [PATCH 173/400] 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 721e13a759..e0b07549f4 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 75a3e45941..9a43d5f031 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 69676a1aed..2c41b3ef26 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 1ec99577ce48557e47ebcb510ac3894be81cd7db Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 24 Apr 2021 14:05:11 +0800 Subject: [PATCH 174/400] Incorrect path on Android --- .github/ISSUE_TEMPLATE/01-bug-issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md index e45893b97a..7026179259 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-issues.md +++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md @@ -25,6 +25,6 @@ Please check: *please attach logs here, which are located at:* - `%AppData%/osu/logs` *(on Windows),* - `~/.local/share/osu/logs` *(on Linux & macOS).* -- `Android/Data/sh.ppy.osulazer/logs` *(on Android)*, +- `Android/data/sh.ppy.osulazer/files/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. (https://support.apple.com/en-us/HT201301#copy-to-computer) --> From 2e2f843e223ead183607062f4fbb43aa9b994a3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Apr 2021 09:26:49 +0300 Subject: [PATCH 175/400] Refine android game logs path in contributing guidelines --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c327f01b3..e14be20642 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in * 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), + * `Android/data/sh.ppy.osulazer/files/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](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), From 7e3a611f95cf9a1ec5503d692bc919c6dc6fb71c Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 16:23:52 +0800 Subject: [PATCH 176/400] Add snap color option for osu!mania --- .../ManiaPlacementBlueprintTestScene.cs | 12 +++ .../ManiaSelectionBlueprintTestScene.cs | 12 +++ .../TestSceneHoldNotePlacementBlueprint.cs | 2 +- .../TestSceneHoldNoteSelectionBlueprint.cs | 2 +- .../Editor/TestSceneManiaHitObjectComposer.cs | 7 +- .../Editor/TestSceneNotePlacementBlueprint.cs | 2 +- .../Editor/TestSceneNoteSelectionBlueprint.cs | 2 +- .../ManiaInputTestScene.cs | 13 +++ .../Mods/TestSceneManiaModPerfect.cs | 4 +- .../Skinning/ManiaSkinnableTestScene.cs | 12 +++ .../Skinning/TestSceneHoldNote.cs | 2 +- .../Skinning/TestSceneNote.cs | 2 +- .../TestSceneAutoGeneration.cs | 24 +++--- .../TestSceneColumn.cs | 4 +- .../TestSceneHoldNoteInput.cs | 75 +++++++++--------- .../TestSceneNotes.cs | 17 +++- .../TestSceneOutOfOrderHits.cs | 10 +-- .../TestSceneStage.cs | 4 +- .../Beatmaps/ManiaBeatmapConverter.cs | 4 +- .../Legacy/DistanceObjectPatternGenerator.cs | 4 +- .../Legacy/EndTimeObjectPatternGenerator.cs | 4 +- .../Legacy/HitObjectPatternGenerator.cs | 2 +- .../ManiaRulesetConfigManager.cs | 4 +- .../Blueprints/HoldNotePlacementBlueprint.cs | 5 +- .../Edit/Blueprints/NotePlacementBlueprint.cs | 5 +- .../Edit/HoldNoteCompositionTool.cs | 7 +- .../Edit/ManiaHitObjectComposer.cs | 4 +- .../Edit/NoteCompositionTool.cs | 8 +- .../ManiaSettingsSubsection.cs | 5 ++ .../Mods/ManiaModInvert.cs | 2 +- .../Objects/Drawables/DrawableNote.cs | 30 +++++++ osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 11 ++- osu.Game.Rulesets.Mania/Objects/Note.cs | 79 +++++++++++++++++++ osu.Game.Rulesets.Mania/Objects/TailNote.cs | 5 ++ .../UI/DrawableManiaRuleset.cs | 5 ++ osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs | 11 +++ .../TestSceneManageCollectionsDialog.cs | 12 +-- 37 files changed, 314 insertions(+), 99 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index ece523e84c..8e94d6bbb6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -28,6 +30,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(typeof(IScrollingInfo))] private IScrollingInfo scrollingInfo; + [Cached] + protected readonly Bindable configColourCode = new Bindable(); + protected ManiaPlacementBlueprintTestScene() { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; @@ -41,6 +46,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + } + protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 176fbba921..453f8e36e6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -2,8 +2,10 @@ // 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.Timing; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -15,6 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); + [Cached] + protected readonly Bindable configColourCode = new Bindable(); + protected ManiaSelectionBlueprintTestScene() { Add(new Column(0) @@ -26,6 +31,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + } + public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs index 87c74a12cf..eb36e19048 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); + protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(null); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs index 24f4c6858e..9674985d09 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestSceneHoldNoteSelectionBlueprint() { - var holdNote = new HoldNote { Column = 0, Duration = 1000 }; + var holdNote = new HoldNote(null) { Column = 0, Duration = 1000 }; holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index aaf96c63a6..1f3f4842c7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddStep("setup beatmap", () => { composer.EditorBeatmap.Clear(); - composer.EditorBeatmap.Add(new HoldNote + composer.EditorBeatmap.Add(new HoldNote(Beatmap.Value.Beatmap) { Column = 1, EndTime = 200 @@ -201,9 +201,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestComposer() { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 4 }); InternalChildren = new Drawable[] { - EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })) + EditorBeatmap = new EditorBeatmap(beatmap) { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo } }, @@ -211,7 +212,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }; for (int i = 0; i < 10; i++) - EditorBeatmap.Add(new Note { StartTime = 125 * i }); + EditorBeatmap.Add(new Note(beatmap) { StartTime = 125 * i }); } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs index 36c34a8fb9..08a22ea28e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs @@ -55,6 +55,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private Note getNote() => this.ChildrenOfType().FirstOrDefault()?.HitObject; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); + protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(null); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs index 0e47a12a8e..caf89599be 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestSceneNoteSelectionBlueprint() { - var note = new Note { Column = 0 }; + var note = new Note(null) { Column = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); DrawableNote drawableObject; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index 9049bb3a82..ea7432f3a7 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -1,15 +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.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { public abstract class ManiaInputTestScene : OsuTestScene { + [Cached] + protected readonly Bindable configColourCode = new Bindable(); private readonly Container content; protected override Container Content => content ?? base.Content; @@ -18,6 +24,13 @@ namespace osu.Game.Rulesets.Mania.Tests base.Content.Add(content = new LocalInputManager(keys)); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + } + private class LocalInputManager : ManiaInputManager { public LocalInputManager(int variant) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 2e3b21aed7..23d0e82439 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(false)] [TestCase(true)] - public void TestNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Note { StartTime = 1000 }), shouldMiss); + public void TestNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Note(Beatmap.Value.Beatmap) { StartTime = 1000 }), shouldMiss); [TestCase(false)] [TestCase(true)] - public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss); + public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1000, EndTime = 3000 }), shouldMiss); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 1d84a2dfcb..3b19386da6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -7,6 +7,8 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + [Cached] + protected readonly Bindable configColourCode = new Bindable(); + protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); protected ManiaSkinnableTestScene() @@ -38,6 +43,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + } + [Test] public void TestScrollingDown() { diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs index e88ff8e2ac..e13d0bbd7f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning protected override DrawableManiaHitObject CreateHitObject() { - var note = new HoldNote { Duration = 1000 }; + var note = new HoldNote(Beatmap.Value.Beatmap) { Duration = 1000 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new DrawableHoldNote(note); diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs index bc3bdf0bcb..eac288872a 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { protected override DrawableManiaHitObject CreateHitObject() { - var note = new Note(); + var note = new Note(Beatmap.Value.Beatmap); note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new DrawableNote(note); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index cffec3dfd5..b2b9d607c6 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -91,8 +91,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -114,8 +114,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -141,8 +141,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 2000, Duration = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -168,8 +168,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 3000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index d9b1ad22fa..59da73a540 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < columns.Count; i++) { - var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; + var obj = new Note(null) { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); columns[i].Add(new DrawableNote(obj)); @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < columns.Count; i++) { - var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; + var obj = new HoldNote(null) { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); columns[i].Add(new DrawableHoldNote(obj)); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 668487f673..c92fe03483 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -249,21 +249,6 @@ namespace osu.Game.Rulesets.Mania.Tests var beatmap = new Beatmap { - HitObjects = - { - new HoldNote - { - StartTime = 1000, - Duration = 500, - Column = 0, - }, - new HoldNote - { - StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10, - Duration = 500, - Column = 0, - }, - }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty @@ -274,6 +259,20 @@ namespace osu.Game.Rulesets.Mania.Tests Ruleset = new ManiaRuleset().RulesetInfo }, }; + beatmap.HitObjects = new List { + new HoldNote(beatmap) + { + StartTime = 1000, + Duration = 500, + Column = 0, + }, + new HoldNote(beatmap) + { + StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10, + Duration = 500, + Column = 0, + }, + }; performTest(new List { @@ -297,21 +296,21 @@ namespace osu.Game.Rulesets.Mania.Tests var beatmap = new Beatmap { - HitObjects = - { - new HoldNote - { - StartTime = time_head, - Duration = time_tail - time_head, - Column = 0, - } - }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = tick_rate }, Ruleset = new ManiaRuleset().RulesetInfo }, }; + beatmap.HitObjects = new List + { + new HoldNote(beatmap) + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + } + }; performTest(new List { @@ -329,17 +328,17 @@ namespace osu.Game.Rulesets.Mania.Tests { var beatmap = new Beatmap { - HitObjects = + BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, + }; + beatmap.HitObjects = new List { - new HoldNote + new HoldNote(beatmap) { StartTime = 1000, Duration = 0, Column = 0, }, - }, - BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, - }; + }; performTest(new List { @@ -374,21 +373,21 @@ namespace osu.Game.Rulesets.Mania.Tests { beatmap = new Beatmap { - HitObjects = - { - new HoldNote - { - StartTime = time_head, - Duration = time_tail - time_head, - Column = 0, - } - }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 }, Ruleset = new ManiaRuleset().RulesetInfo }, }; + beatmap.HitObjects = new List + { + new HoldNote(beatmap) + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + } + }; beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 706268e478..3660895347 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -15,8 +15,10 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI.Scrolling; @@ -29,6 +31,10 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { + + [Cached] + protected readonly Bindable configColourCode = new Bindable(); + [Test] public void TestVariousNotes() { @@ -63,9 +69,16 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("hold note 2 facing upwards", () => verifyAnchors(holdNote2, Anchor.y0)); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + } + private Drawable createNoteDisplay(ScrollingDirection direction, int identifier, out DrawableNote hitObject) { - var note = new Note { StartTime = 0 }; + var note = new Note(null) { StartTime = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new ScrollingTestContainer(direction) @@ -80,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Tests private Drawable createHoldNoteDisplay(ScrollingDirection direction, int identifier, out DrawableHoldNote hitObject) { - var note = new HoldNote { StartTime = 0, Duration = 5000 }; + var note = new HoldNote(null) { StartTime = 0, Duration = 5000 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index 18891f8c58..65195fdd3f 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests { double time = 1000 + i * 100; - objects.Add(new Note { StartTime = time }); + objects.Add(new Note(Beatmap.Value.Beatmap) { StartTime = time }); // don't hit the first note if (i > 0) @@ -60,12 +60,12 @@ namespace osu.Game.Rulesets.Mania.Tests { var objects = new List { - new HoldNote + new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1000, EndTime = 1010, }, - new HoldNote + new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1020, EndTime = 1030 @@ -83,12 +83,12 @@ namespace osu.Game.Rulesets.Mania.Tests { var objects = new List { - new HoldNote + new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1000, EndTime = 1010, }, - new HoldNote + new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1020, EndTime = 1030 diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index 7376a90f17..777163cca7 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < stage.Columns.Count; i++) { - var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; + var obj = new Note(null) { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); stage.Add(new DrawableNote(obj)); @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < stage.Columns.Count; i++) { - var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; + var obj = new HoldNote(null) { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); stage.Add(new DrawableHoldNote(obj)); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 26393c8edb..5f03c93d55 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (HitObject is IHasDuration endTimeData) { - pattern.Add(new HoldNote + pattern.Add(new HoldNote(Beatmap) { StartTime = HitObject.StartTime, Duration = endTimeData.Duration, @@ -258,7 +258,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } else if (HitObject is IHasXPosition) { - pattern.Add(new Note + pattern.Add(new Note(Beatmap) { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 26e5d381e2..a936878a38 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -512,7 +512,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (startTime == endTime) { - newObject = new Note + newObject = new Note(Beatmap) { StartTime = startTime, Samples = sampleInfoListAt(startTime), @@ -521,7 +521,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } else { - newObject = new HoldNote + newObject = new HoldNote(Beatmap) { StartTime = startTime, Duration = endTime - startTime, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index f816a70ab3..9422146803 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (holdNote) { - newObject = new HoldNote + newObject = new HoldNote(Beatmap) { StartTime = HitObject.StartTime, Duration = endTime - HitObject.StartTime, @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } else { - newObject = new Note + newObject = new Note(Beatmap) { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 54c37e9742..bbb4d4b466 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -441,7 +441,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The column to add the note to. private void addToPattern(Pattern pattern, int column) { - pattern.Add(new Note + pattern.Add(new Note(Beatmap) { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 39d0f4bae4..292d494d88 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Mania.Configuration SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); + SetDefault(ManiaRulesetSetting.ColourCode, ManiaColourCode.Off); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings @@ -34,6 +35,7 @@ namespace osu.Game.Rulesets.Mania.Configuration public enum ManiaRulesetSetting { ScrollTime, - ScrollDirection + ScrollDirection, + ColourCode } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 093a8da24f..b3902524a0 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -23,8 +24,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } - public HoldNotePlacementBlueprint() - : base(new HoldNote()) + public HoldNotePlacementBlueprint(IBeatmap beatmap) + : base(new HoldNote(beatmap)) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index 3db89c8ae6..cc0ef5181a 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.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -14,8 +15,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { private readonly EditNotePiece piece; - public NotePlacementBlueprint() - : base(new Note()) + public NotePlacementBlueprint(IBeatmap beatmap) + : base(new Note(beatmap)) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs index a5f10ed436..696278de4d 100644 --- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -5,19 +5,22 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit.Blueprints; namespace osu.Game.Rulesets.Mania.Edit { public class HoldNoteCompositionTool : HitObjectCompositionTool { - public HoldNoteCompositionTool() + private ManiaBeatmap Beatmap; + public HoldNoteCompositionTool(ManiaBeatmap beatmap) : base("Hold") { + Beatmap = beatmap; } public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); + public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(Beatmap); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index d9570bf8be..0defeb026d 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -93,8 +93,8 @@ namespace osu.Game.Rulesets.Mania.Edit protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { - new NoteCompositionTool(), - new HoldNoteCompositionTool() + new NoteCompositionTool(drawableRuleset.Beatmap), + new HoldNoteCompositionTool(drawableRuleset.Beatmap) }; protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs index 9f54152596..c77c3722c6 100644 --- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; @@ -12,13 +13,16 @@ namespace osu.Game.Rulesets.Mania.Edit { public class NoteCompositionTool : HitObjectCompositionTool { - public NoteCompositionTool() + private ManiaBeatmap Beatmap; + + public NoteCompositionTool(ManiaBeatmap beatmap) : base(nameof(Note)) { + Beatmap = beatmap; } public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); + public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(Beatmap); } } diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index de77af8306..d46076d3bc 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Mania Current = config.GetBindable(ManiaRulesetSetting.ScrollTime), KeyboardStep = 5 }, + new SettingsEnumDropdown + { + LabelText = "Colour-coded notes", + Current = config.GetBindable(ManiaRulesetSetting.ColourCode), + } }; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 1ea45c295c..11310c6642 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Mods // Decrease the duration by at most a 1/4 beat to ensure there's no instantaneous notes. duration = Math.Max(duration / 2, duration - beatLength / 4); - newColumnObjects.Add(new HoldNote + newColumnObjects.Add(new HoldNote(maniaBeatmap) { Column = column.Key, StartTime = locations[i].startTime, diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index b512986ccb..436e0923c4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -2,12 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Skinning.Default; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Objects.Drawables @@ -17,6 +21,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler { + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private Bindable configColourCode { get; set; } + protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; private readonly Drawable headPiece; @@ -34,6 +44,26 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + HitObject.SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCode.Value, snap.NewValue), true); + configColourCode.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, HitObject.Snap)); + } + + private void UpdateSnapColour(ManiaColourCode colourCode, int snap) + { + if (colourCode == ManiaColourCode.On) + { + Colour = BindableBeatDivisor.GetColourFor(HitObject.Snap, colours); + } + else + { + Colour = Colour4.White; + } + } + protected override void OnDirectionChanged(ValueChangedEvent e) { base.OnDirectionChanged(e); diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 6cc7ff92d3..bf0631170a 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Objects /// public class HoldNote : ManiaHitObject, IHasDuration { + public IBeatmap Beatmap; + public double EndTime { get => StartTime + Duration; @@ -84,6 +86,11 @@ namespace osu.Game.Rulesets.Mania.Objects /// private double tickSpacing = 50; + public HoldNote(IBeatmap beatmap) : base() + { + Beatmap = beatmap; + } + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -98,14 +105,14 @@ namespace osu.Game.Rulesets.Mania.Objects createTicks(cancellationToken); - AddNested(Head = new Note + AddNested(Head = new Note(Beatmap) { StartTime = StartTime, Column = Column, Samples = GetNodeSamples(0), }); - AddNested(Tail = new TailNote + AddNested(Tail = new TailNote(Beatmap) { StartTime = EndTime, Column = Column, diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 0035960c63..6ee0232c5b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -1,6 +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 osu.Framework.Bindables; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; @@ -12,5 +17,79 @@ namespace osu.Game.Rulesets.Mania.Objects public class Note : ManiaHitObject { public override Judgement CreateJudgement() => new ManiaJudgement(); + + private IBeatmap Beatmap; + + public readonly Bindable SnapBindable = new Bindable(); + + public int Snap + { + get => SnapBindable.Value; + set => SnapBindable.Value = value; + } + + public Note(IBeatmap beatmap) : base() + { + Beatmap = beatmap; + this.StartTimeBindable.BindValueChanged(_ => SnapToBeatmap(), true); + } + + private void SnapToBeatmap() + { + if (Beatmap != null) + { + TimingControlPoint currentTimingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime); + int timeSignature = (int)currentTimingPoint.TimeSignature; + double startTime = currentTimingPoint.Time; + double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; + + double offset = startTime % secondsPerFourCounts; + double snapResult = StartTime % secondsPerFourCounts - offset; + + if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) + { + Snap = 1; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) + { + Snap = 2; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) + { + Snap = 3; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) + { + Snap = 4; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) + { + Snap = 6; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) + { + Snap = 8; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) + { + Snap = 12; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) + { + Snap = 16; + } + else + { + Snap = 0; + } + } + } + + private const double LENIENCY_MS = 1.0; + private static bool AlmostDivisibleBy(double dividend, double divisor) + { + double remainder = Math.Abs(dividend) % divisor; + return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); + } } } diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs index 5a30fd6a12..bc56408691 100644 --- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/TailNote.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.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; @@ -8,6 +9,10 @@ namespace osu.Game.Rulesets.Mania.Objects { public class TailNote : Note { + public TailNote(IBeatmap beatmap) : base(beatmap) + { + } + public override Judgement CreateJudgement() => new ManiaJudgement(); } } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 4ee060e91e..dc9c525cb0 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -51,6 +51,9 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; + [Cached] + protected readonly Bindable configColourCode = new Bindable(); + public ScrollVisualisationMethod ScrollMethod { get => scrollMethod; @@ -104,6 +107,8 @@ namespace osu.Game.Rulesets.Mania.UI configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); + + Config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); } protected override void AdjustScrollSpeed(int amount) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs b/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs new file mode 100644 index 0000000000..91b637b71d --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaColourCode.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.Rulesets.Mania.UI +{ + public enum ManiaColourCode + { + Off, + On + } +} diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index eca857f9e5..d8d419bceb 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -140,12 +140,12 @@ namespace osu.Game.Tests.Visual.Collections AddStep("add dropdown", () => { Add(new CollectionFilterDropdown - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - Width = 0.4f, - } + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, + } ); }); AddStep("add two collections with same name", () => manager.Collections.AddRange(new[] From 59ae5ab913f74d75677576e10d3b3a9484a713a7 Mon Sep 17 00:00:00 2001 From: Denrage Date: Sat, 24 Apr 2021 13:25:29 +0200 Subject: [PATCH 177/400] Added transition in StarRatingDisplay --- .../Ranking/Expanded/StarRatingDisplay.cs | 112 +++++++++--------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index bfc336a677..748f58e430 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -27,9 +26,8 @@ namespace osu.Game.Screens.Ranking.Expanded private OsuColour colours { get; set; } private CircularContainer colorContainer; - private OsuSpriteText wholePartText; - private OsuSpriteText fractionPartText; private StarDifficulty starDifficulty; + private FillFlowContainer foregroundContainer; public StarDifficulty StarDifficulty { @@ -52,23 +50,15 @@ namespace osu.Game.Screens.Ranking.Expanded private void setDifficulty(StarDifficulty difficulty) { - var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); - string wholePart = starRatingParts[0]; - string fractionPart = starRatingParts[1]; - string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + colorContainer.FadeColour(getDifficultyColour(difficulty), 250); - ColourInfo backgroundColour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus - ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); - - colorContainer.Colour = backgroundColour; - - wholePartText.Text = $"{wholePart}"; - fractionPartText.Text = $"{separator}{fractionPart}"; + foregroundContainer.Expire(); + foregroundContainer = null; + AddInternal(foregroundContainer = createForegroundContainer(difficulty)); } [BackgroundDependencyLoader] - private void load(BeatmapDifficultyCache difficultyCache) + private void load() { AutoSizeAxes = Axes.Both; @@ -78,6 +68,7 @@ namespace osu.Game.Screens.Ranking.Expanded { RelativeSizeAxes = Axes.Both, Masking = true, + Colour = getDifficultyColour(starDifficulty), Children = new Drawable[] { new Box @@ -86,49 +77,64 @@ namespace osu.Game.Screens.Ranking.Expanded }, } }, - new FillFlowContainer + foregroundContainer = createForegroundContainer(starDifficulty), + }; + } + + private ColourInfo getDifficultyColour(StarDifficulty difficulty) + { + return difficulty.DifficultyRating == DifficultyRating.ExpertPlus + ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) + : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); + } + + private FillFlowContainer createForegroundContainer(StarDifficulty difficulty) + { + var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); + string wholePart = starRatingParts[0]; + string fractionPart = starRatingParts[1]; + string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + + return new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 8, Vertical = 4 }, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2, 0), + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 8, Vertical = 4 }, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(2, 0), - Children = new Drawable[] + new SpriteIcon { - new SpriteIcon + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(7), + Icon = FontAwesome.Solid.Star, + Colour = Color4.Black + }, + new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + TextAnchor = Anchor.BottomLeft, + }.With(t => + { + t.AddText($"{wholePart}", s => { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(7), - Icon = FontAwesome.Solid.Star, - Colour = Color4.Black - }, - new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 14); + s.UseFullGlyphHeight = false; + }); + t.AddText($"{separator}{fractionPart}", s => { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - TextAnchor = Anchor.BottomLeft, - }.With(t => - { - t.AddText(wholePartText = new OsuSpriteText(), s => - { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 14); - s.UseFullGlyphHeight = false; - }); - t.AddText(fractionPartText = new OsuSpriteText(), s => - { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 7); - s.UseFullGlyphHeight = false; - }); - }), - } + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 7); + s.UseFullGlyphHeight = false; + }); + }), } }; - - setDifficulty(starDifficulty); } } } From f9905ebe68352f19f6fafa981369da2a4a4faeba Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 19:30:16 +0800 Subject: [PATCH 178/400] Remove beatmap argument in Note --- .../ManiaPlacementBlueprintTestScene.cs | 12 --- .../ManiaSelectionBlueprintTestScene.cs | 12 --- .../TestSceneHoldNotePlacementBlueprint.cs | 2 +- .../TestSceneHoldNoteSelectionBlueprint.cs | 2 +- .../Editor/TestSceneManiaHitObjectComposer.cs | 7 +- .../Editor/TestSceneNotePlacementBlueprint.cs | 2 +- .../Editor/TestSceneNoteSelectionBlueprint.cs | 2 +- .../ManiaInputTestScene.cs | 13 ---- .../Mods/TestSceneManiaModPerfect.cs | 4 +- .../Skinning/ManiaSkinnableTestScene.cs | 12 --- .../Skinning/TestSceneHoldNote.cs | 2 +- .../Skinning/TestSceneNote.cs | 2 +- .../TestSceneAutoGeneration.cs | 24 +++--- .../TestSceneColumn.cs | 4 +- .../TestSceneHoldNoteInput.cs | 75 ++++++++++--------- .../TestSceneNotes.cs | 17 +---- .../TestSceneOutOfOrderHits.cs | 10 +-- .../TestSceneStage.cs | 4 +- .../Beatmaps/ManiaBeatmapConverter.cs | 4 +- .../Legacy/DistanceObjectPatternGenerator.cs | 4 +- .../Legacy/EndTimeObjectPatternGenerator.cs | 4 +- .../Legacy/HitObjectPatternGenerator.cs | 2 +- .../Blueprints/HoldNotePlacementBlueprint.cs | 5 +- .../Edit/Blueprints/NotePlacementBlueprint.cs | 5 +- .../Edit/HoldNoteCompositionTool.cs | 7 +- .../Edit/ManiaHitObjectComposer.cs | 4 +- .../Edit/NoteCompositionTool.cs | 8 +- .../Mods/ManiaModInvert.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 11 +-- osu.Game.Rulesets.Mania/Objects/Note.cs | 3 +- osu.Game.Rulesets.Mania/Objects/TailNote.cs | 5 -- .../TestSceneManageCollectionsDialog.cs | 12 +-- 32 files changed, 99 insertions(+), 183 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index 8e94d6bbb6..ece523e84c 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -4,12 +4,10 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -30,9 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(typeof(IScrollingInfo))] private IScrollingInfo scrollingInfo; - [Cached] - protected readonly Bindable configColourCode = new Bindable(); - protected ManiaPlacementBlueprintTestScene() { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; @@ -46,13 +41,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); - } - protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 453f8e36e6..176fbba921 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -2,10 +2,8 @@ // 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.Timing; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -17,9 +15,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); - [Cached] - protected readonly Bindable configColourCode = new Bindable(); - protected ManiaSelectionBlueprintTestScene() { Add(new Column(0) @@ -31,13 +26,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); - } - public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs index eb36e19048..87c74a12cf 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(null); + protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs index 9674985d09..24f4c6858e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestSceneHoldNoteSelectionBlueprint() { - var holdNote = new HoldNote(null) { Column = 0, Duration = 1000 }; + var holdNote = new HoldNote { Column = 0, Duration = 1000 }; holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 1f3f4842c7..aaf96c63a6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddStep("setup beatmap", () => { composer.EditorBeatmap.Clear(); - composer.EditorBeatmap.Add(new HoldNote(Beatmap.Value.Beatmap) + composer.EditorBeatmap.Add(new HoldNote { Column = 1, EndTime = 200 @@ -201,10 +201,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestComposer() { - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 4 }); InternalChildren = new Drawable[] { - EditorBeatmap = new EditorBeatmap(beatmap) + EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })) { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo } }, @@ -212,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }; for (int i = 0; i < 10; i++) - EditorBeatmap.Add(new Note(beatmap) { StartTime = 125 * i }); + EditorBeatmap.Add(new Note { StartTime = 125 * i }); } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs index 08a22ea28e..36c34a8fb9 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs @@ -55,6 +55,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private Note getNote() => this.ChildrenOfType().FirstOrDefault()?.HitObject; protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); - protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(null); + protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs index caf89599be..0e47a12a8e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public TestSceneNoteSelectionBlueprint() { - var note = new Note(null) { Column = 0 }; + var note = new Note { Column = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); DrawableNote drawableObject; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index ea7432f3a7..9049bb3a82 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -1,21 +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.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { public abstract class ManiaInputTestScene : OsuTestScene { - [Cached] - protected readonly Bindable configColourCode = new Bindable(); private readonly Container content; protected override Container Content => content ?? base.Content; @@ -24,13 +18,6 @@ namespace osu.Game.Rulesets.Mania.Tests base.Content.Add(content = new LocalInputManager(keys)); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); - } - private class LocalInputManager : ManiaInputManager { public LocalInputManager(int variant) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 23d0e82439..2e3b21aed7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -19,10 +19,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(false)] [TestCase(true)] - public void TestNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Note(Beatmap.Value.Beatmap) { StartTime = 1000 }), shouldMiss); + public void TestNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Note { StartTime = 1000 }), shouldMiss); [TestCase(false)] [TestCase(true)] - public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote(Beatmap.Value.Beatmap) { StartTime = 1000, EndTime = 3000 }), shouldMiss); + public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 3b19386da6..1d84a2dfcb 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -7,8 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -26,9 +24,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); - [Cached] - protected readonly Bindable configColourCode = new Bindable(); - protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); protected ManiaSkinnableTestScene() @@ -43,13 +38,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); - } - [Test] public void TestScrollingDown() { diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs index e13d0bbd7f..e88ff8e2ac 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning protected override DrawableManiaHitObject CreateHitObject() { - var note = new HoldNote(Beatmap.Value.Beatmap) { Duration = 1000 }; + var note = new HoldNote { Duration = 1000 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new DrawableHoldNote(note); diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs index eac288872a..bc3bdf0bcb 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { protected override DrawableManiaHitObject CreateHitObject() { - var note = new Note(Beatmap.Value.Beatmap); + var note = new Note(); note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new DrawableNote(note); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index b2b9d607c6..cffec3dfd5 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000, Column = 1 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -91,8 +91,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -114,8 +114,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 1000 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 2000, Column = 1 }); + beatmap.HitObjects.Add(new Note { StartTime = 1000 }); + beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -141,8 +141,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 2000, Duration = 2000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); @@ -168,8 +168,8 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote(beatmap) { StartTime = 1000, Duration = 2000 }); - beatmap.HitObjects.Add(new Note(beatmap) { StartTime = 3000, Column = 1 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); + beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 59da73a540..d9b1ad22fa 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < columns.Count; i++) { - var obj = new Note(null) { Column = i, StartTime = Time.Current + 2000 }; + var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); columns[i].Add(new DrawableNote(obj)); @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < columns.Count; i++) { - var obj = new HoldNote(null) { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; + var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); columns[i].Add(new DrawableHoldNote(obj)); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index c92fe03483..668487f673 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -249,6 +249,21 @@ namespace osu.Game.Rulesets.Mania.Tests var beatmap = new Beatmap { + HitObjects = + { + new HoldNote + { + StartTime = 1000, + Duration = 500, + Column = 0, + }, + new HoldNote + { + StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10, + Duration = 500, + Column = 0, + }, + }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty @@ -259,20 +274,6 @@ namespace osu.Game.Rulesets.Mania.Tests Ruleset = new ManiaRuleset().RulesetInfo }, }; - beatmap.HitObjects = new List { - new HoldNote(beatmap) - { - StartTime = 1000, - Duration = 500, - Column = 0, - }, - new HoldNote(beatmap) - { - StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10, - Duration = 500, - Column = 0, - }, - }; performTest(new List { @@ -296,21 +297,21 @@ namespace osu.Game.Rulesets.Mania.Tests var beatmap = new Beatmap { + HitObjects = + { + new HoldNote + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + } + }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = tick_rate }, Ruleset = new ManiaRuleset().RulesetInfo }, }; - beatmap.HitObjects = new List - { - new HoldNote(beatmap) - { - StartTime = time_head, - Duration = time_tail - time_head, - Column = 0, - } - }; performTest(new List { @@ -328,17 +329,17 @@ namespace osu.Game.Rulesets.Mania.Tests { var beatmap = new Beatmap { - BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, - }; - beatmap.HitObjects = new List + HitObjects = { - new HoldNote(beatmap) + new HoldNote { StartTime = 1000, Duration = 0, Column = 0, }, - }; + }, + BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, + }; performTest(new List { @@ -373,21 +374,21 @@ namespace osu.Game.Rulesets.Mania.Tests { beatmap = new Beatmap { + HitObjects = + { + new HoldNote + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + } + }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 }, Ruleset = new ManiaRuleset().RulesetInfo }, }; - beatmap.HitObjects = new List - { - new HoldNote(beatmap) - { - StartTime = time_head, - Duration = time_tail - time_head, - Column = 0, - } - }; beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 3660895347..706268e478 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -15,10 +15,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI.Scrolling; @@ -31,10 +29,6 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { - - [Cached] - protected readonly Bindable configColourCode = new Bindable(); - [Test] public void TestVariousNotes() { @@ -69,16 +63,9 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("hold note 2 facing upwards", () => verifyAnchors(holdNote2, Anchor.y0)); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); - } - private Drawable createNoteDisplay(ScrollingDirection direction, int identifier, out DrawableNote hitObject) { - var note = new Note(null) { StartTime = 0 }; + var note = new Note { StartTime = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new ScrollingTestContainer(direction) @@ -93,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.Tests private Drawable createHoldNoteDisplay(ScrollingDirection direction, int identifier, out DrawableHoldNote hitObject) { - var note = new HoldNote(null) { StartTime = 0, Duration = 5000 }; + var note = new HoldNote { StartTime = 0, Duration = 5000 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index 65195fdd3f..18891f8c58 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests { double time = 1000 + i * 100; - objects.Add(new Note(Beatmap.Value.Beatmap) { StartTime = time }); + objects.Add(new Note { StartTime = time }); // don't hit the first note if (i > 0) @@ -60,12 +60,12 @@ namespace osu.Game.Rulesets.Mania.Tests { var objects = new List { - new HoldNote(Beatmap.Value.Beatmap) + new HoldNote { StartTime = 1000, EndTime = 1010, }, - new HoldNote(Beatmap.Value.Beatmap) + new HoldNote { StartTime = 1020, EndTime = 1030 @@ -83,12 +83,12 @@ namespace osu.Game.Rulesets.Mania.Tests { var objects = new List { - new HoldNote(Beatmap.Value.Beatmap) + new HoldNote { StartTime = 1000, EndTime = 1010, }, - new HoldNote(Beatmap.Value.Beatmap) + new HoldNote { StartTime = 1020, EndTime = 1030 diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index 777163cca7..7376a90f17 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < stage.Columns.Count; i++) { - var obj = new Note(null) { Column = i, StartTime = Time.Current + 2000 }; + var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); stage.Add(new DrawableNote(obj)); @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Tests { for (int i = 0; i < stage.Columns.Count; i++) { - var obj = new HoldNote(null) { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; + var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); stage.Add(new DrawableHoldNote(obj)); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 5f03c93d55..26393c8edb 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (HitObject is IHasDuration endTimeData) { - pattern.Add(new HoldNote(Beatmap) + pattern.Add(new HoldNote { StartTime = HitObject.StartTime, Duration = endTimeData.Duration, @@ -258,7 +258,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } else if (HitObject is IHasXPosition) { - pattern.Add(new Note(Beatmap) + pattern.Add(new Note { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index a936878a38..26e5d381e2 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -512,7 +512,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (startTime == endTime) { - newObject = new Note(Beatmap) + newObject = new Note { StartTime = startTime, Samples = sampleInfoListAt(startTime), @@ -521,7 +521,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } else { - newObject = new HoldNote(Beatmap) + newObject = new HoldNote { StartTime = startTime, Duration = endTime - startTime, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 9422146803..f816a70ab3 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (holdNote) { - newObject = new HoldNote(Beatmap) + newObject = new HoldNote { StartTime = HitObject.StartTime, Duration = endTime - HitObject.StartTime, @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } else { - newObject = new Note(Beatmap) + newObject = new Note { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index bbb4d4b466..54c37e9742 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -441,7 +441,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The column to add the note to. private void addToPattern(Pattern pattern, int column) { - pattern.Add(new Note(Beatmap) + pattern.Add(new Note { StartTime = HitObject.StartTime, Samples = HitObject.Samples, diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index b3902524a0..093a8da24f 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -24,8 +23,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } - public HoldNotePlacementBlueprint(IBeatmap beatmap) - : base(new HoldNote(beatmap)) + public HoldNotePlacementBlueprint() + : base(new HoldNote()) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index cc0ef5181a..3db89c8ae6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; @@ -15,8 +14,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { private readonly EditNotePiece piece; - public NotePlacementBlueprint(IBeatmap beatmap) - : base(new Note(beatmap)) + public NotePlacementBlueprint() + : base(new Note()) { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs index 696278de4d..a5f10ed436 100644 --- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -5,22 +5,19 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit.Blueprints; namespace osu.Game.Rulesets.Mania.Edit { public class HoldNoteCompositionTool : HitObjectCompositionTool { - private ManiaBeatmap Beatmap; - public HoldNoteCompositionTool(ManiaBeatmap beatmap) + public HoldNoteCompositionTool() : base("Hold") { - Beatmap = beatmap; } public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); - public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(Beatmap); + public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 0defeb026d..d9570bf8be 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -93,8 +93,8 @@ namespace osu.Game.Rulesets.Mania.Edit protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { - new NoteCompositionTool(drawableRuleset.Beatmap), - new HoldNoteCompositionTool(drawableRuleset.Beatmap) + new NoteCompositionTool(), + new HoldNoteCompositionTool() }; protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs index c77c3722c6..9f54152596 100644 --- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; @@ -13,16 +12,13 @@ namespace osu.Game.Rulesets.Mania.Edit { public class NoteCompositionTool : HitObjectCompositionTool { - private ManiaBeatmap Beatmap; - - public NoteCompositionTool(ManiaBeatmap beatmap) + public NoteCompositionTool() : base(nameof(Note)) { - Beatmap = beatmap; } public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); - public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(Beatmap); + public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 11310c6642..1ea45c295c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Mods // Decrease the duration by at most a 1/4 beat to ensure there's no instantaneous notes. duration = Math.Max(duration / 2, duration - beatLength / 4); - newColumnObjects.Add(new HoldNote(maniaBeatmap) + newColumnObjects.Add(new HoldNote { Column = column.Key, StartTime = locations[i].startTime, diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index bf0631170a..6cc7ff92d3 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Mania.Objects /// public class HoldNote : ManiaHitObject, IHasDuration { - public IBeatmap Beatmap; - public double EndTime { get => StartTime + Duration; @@ -86,11 +84,6 @@ namespace osu.Game.Rulesets.Mania.Objects /// private double tickSpacing = 50; - public HoldNote(IBeatmap beatmap) : base() - { - Beatmap = beatmap; - } - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -105,14 +98,14 @@ namespace osu.Game.Rulesets.Mania.Objects createTicks(cancellationToken); - AddNested(Head = new Note(Beatmap) + AddNested(Head = new Note { StartTime = StartTime, Column = Column, Samples = GetNodeSamples(0), }); - AddNested(Tail = new TailNote(Beatmap) + AddNested(Tail = new TailNote { StartTime = EndTime, Column = Column, diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 6ee0232c5b..36f32c78af 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -28,9 +28,8 @@ namespace osu.Game.Rulesets.Mania.Objects set => SnapBindable.Value = value; } - public Note(IBeatmap beatmap) : base() + public Note() { - Beatmap = beatmap; this.StartTimeBindable.BindValueChanged(_ => SnapToBeatmap(), true); } diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs index bc56408691..5a30fd6a12 100644 --- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/TailNote.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.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; @@ -9,10 +8,6 @@ namespace osu.Game.Rulesets.Mania.Objects { public class TailNote : Note { - public TailNote(IBeatmap beatmap) : base(beatmap) - { - } - public override Judgement CreateJudgement() => new ManiaJudgement(); } } diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index d8d419bceb..eca857f9e5 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -140,12 +140,12 @@ namespace osu.Game.Tests.Visual.Collections AddStep("add dropdown", () => { Add(new CollectionFilterDropdown - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - Width = 0.4f, - } + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, + } ); }); AddStep("add two collections with same name", () => manager.Collections.AddRange(new[] From d6d81fb8e5a55db4f58aa4cc41d729f3e0ec3f27 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 19:53:21 +0800 Subject: [PATCH 179/400] Move color snap logic from Note to DrawableNote --- .../Objects/Drawables/DrawableNote.cs | 102 +++++++++++++++--- osu.Game.Rulesets.Mania/Objects/Note.cs | 78 -------------- .../UI/DrawableManiaRuleset.cs | 5 + 3 files changed, 93 insertions(+), 92 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 436e0923c4..3b2a9feee2 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Scoring; @@ -27,10 +31,21 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private Bindable configColourCode { get; set; } + [Resolved(canBeNull: true)] + private ManiaBeatmap beatmap { get; set; } + protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; private readonly Drawable headPiece; + public readonly Bindable SnapBindable = new Bindable(); + + public int Snap + { + get => SnapBindable.Value; + set => SnapBindable.Value = value; + } + public DrawableNote(Note hitObject) : base(hitObject) { @@ -48,20 +63,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.LoadComplete(); - HitObject.SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCode.Value, snap.NewValue), true); - configColourCode.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, HitObject.Snap)); - } + HitObject.StartTimeBindable.BindValueChanged(_ => SnapToBeatmap(), true); - private void UpdateSnapColour(ManiaColourCode colourCode, int snap) - { - if (colourCode == ManiaColourCode.On) - { - Colour = BindableBeatDivisor.GetColourFor(HitObject.Snap, colours); - } - else - { - Colour = Colour4.White; - } + SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCode.Value, snap.NewValue), true); + configColourCode.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, Snap)); } protected override void OnDirectionChanged(ValueChangedEvent e) @@ -103,5 +108,74 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public virtual void OnReleased(ManiaAction action) { } + private void SnapToBeatmap() + { + if (beatmap != null) + { + TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime); + int timeSignature = (int)currentTimingPoint.TimeSignature; + double startTime = currentTimingPoint.Time; + double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; + + double offset = startTime % secondsPerFourCounts; + double snapResult = HitObject.StartTime % secondsPerFourCounts - offset; + + if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) + { + Snap = 1; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) + { + Snap = 2; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) + { + Snap = 3; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) + { + Snap = 4; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) + { + Snap = 6; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) + { + Snap = 8; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) + { + Snap = 12; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) + { + Snap = 16; + } + else + { + Snap = 0; + } + } + } + + private const double LENIENCY_MS = 1.0; + private static bool AlmostDivisibleBy(double dividend, double divisor) + { + double remainder = Math.Abs(dividend) % divisor; + return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); + } + + private void UpdateSnapColour(ManiaColourCode colourCode, int snap) + { + if (colourCode == ManiaColourCode.On) + { + Colour = BindableBeatDivisor.GetColourFor(Snap, colours); + } + else + { + Colour = Colour4.White; + } + } } -} +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 36f32c78af..0035960c63 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -1,11 +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 osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; @@ -17,78 +12,5 @@ namespace osu.Game.Rulesets.Mania.Objects public class Note : ManiaHitObject { public override Judgement CreateJudgement() => new ManiaJudgement(); - - private IBeatmap Beatmap; - - public readonly Bindable SnapBindable = new Bindable(); - - public int Snap - { - get => SnapBindable.Value; - set => SnapBindable.Value = value; - } - - public Note() - { - this.StartTimeBindable.BindValueChanged(_ => SnapToBeatmap(), true); - } - - private void SnapToBeatmap() - { - if (Beatmap != null) - { - TimingControlPoint currentTimingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime); - int timeSignature = (int)currentTimingPoint.TimeSignature; - double startTime = currentTimingPoint.Time; - double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; - - double offset = startTime % secondsPerFourCounts; - double snapResult = StartTime % secondsPerFourCounts - offset; - - if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) - { - Snap = 1; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) - { - Snap = 2; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) - { - Snap = 3; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) - { - Snap = 4; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) - { - Snap = 6; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) - { - Snap = 8; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) - { - Snap = 12; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) - { - Snap = 16; - } - else - { - Snap = 0; - } - } - } - - private const double LENIENCY_MS = 1.0; - private static bool AlmostDivisibleBy(double dividend, double divisor) - { - double remainder = Math.Abs(dividend) % divisor; - return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); - } } } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index dc9c525cb0..850d4800cd 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -45,6 +45,9 @@ namespace osu.Game.Rulesets.Mania.UI public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; + [Cached(typeof(ManiaBeatmap))] + private ManiaBeatmap CachedBeatmap { get; } + public IEnumerable BarLines; protected override bool RelativeScaleBeatLengths => true; @@ -80,6 +83,8 @@ namespace osu.Game.Rulesets.Mania.UI : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; + + CachedBeatmap = Beatmap; } [BackgroundDependencyLoader] From e937b778f6a5ccb4840e9561467c81edfbfb4028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Apr 2021 14:19:39 +0200 Subject: [PATCH 180/400] Fix potential failure in `ensureSourceClockSet()` `ensureSourceClockSet()` was intended to only run when the adjustable source hasn't been set at all yet. As it turns out permitting it to run unconditionally can break the state of the underlying interpolated clock. This is caused by the following factors: * While the decoupleable clock is running, its `CurrentTime` does not come from either the source clock, or the internal stopwatch; it is instead calculated using the base `InterpolatingFramedClock` logic. * A source change of a decoupleable clock seeks the provided source clock to the decoupleable's current time. * When an interpolating clock is seeked (decoupleable clock is also an interpolating one), its interpolation state (`{Last,Current}InterpolatedTime`) are reset to 0. * If the interpolating clock determines that its current time is too far away from the source's time (which was set when the source is changed), it will ignore the source and instead continue to use its current time until the source clock has caught up. Overall, the source change is not really necessary if a source is already there. The only reason to ensure it was set was to make sure the first seek of the gameplay clock wasn't performed in decoupled mode. Therefore, add a guard to make sure the source is only set if there isn't one already. --- osu.Game/Screens/Play/GameplayClockContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6d60c09521..1c8a3e51ac 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -116,13 +116,17 @@ namespace osu.Game.Screens.Play protected void ChangeSource(IClock sourceClock) => AdjustableSource.ChangeSource(SourceClock = sourceClock); /// - /// Ensures that the is set to . + /// Ensures that the is set to , if it hasn't been given a source yet. /// This is usually done before a seek to avoid accidentally seeking only the adjustable source in decoupled mode, /// but not the actual source clock. /// That will pretty much only happen on the very first call of this method, as the source clock is passed in the constructor, /// but it is not yet set on the adjustable source there. /// - private void ensureSourceClockSet() => ChangeSource(SourceClock); + private void ensureSourceClockSet() + { + if (AdjustableSource.Source == null) + ChangeSource(SourceClock); + } protected override void Update() { From a8b401522bf561d5a2d49567ec99a2048e56586a Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 20:39:22 +0800 Subject: [PATCH 181/400] Remove ManiaColourCode in favor for boolean --- .../Configuration/ManiaRulesetConfigManager.cs | 2 +- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 4 ++-- .../Objects/Drawables/DrawableNote.cs | 6 +++--- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs | 11 ----------- 5 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 292d494d88..f37937a736 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Configuration SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); - SetDefault(ManiaRulesetSetting.ColourCode, ManiaColourCode.Off); + SetDefault(ManiaRulesetSetting.ColourCode, false); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index d46076d3bc..c0c587f67c 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -37,10 +37,10 @@ namespace osu.Game.Rulesets.Mania Current = config.GetBindable(ManiaRulesetSetting.ScrollTime), KeyboardStep = 5 }, - new SettingsEnumDropdown + new SettingsCheckbox { LabelText = "Colour-coded notes", - Current = config.GetBindable(ManiaRulesetSetting.ColourCode), + Current = config.GetBindable(ManiaRulesetSetting.ColourCode), } }; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 3b2a9feee2..7cda70f2af 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private OsuColour colours { get; set; } [Resolved] - private Bindable configColourCode { get; set; } + private Bindable configColourCode { get; set; } [Resolved(canBeNull: true)] private ManiaBeatmap beatmap { get; set; } @@ -166,9 +166,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); } - private void UpdateSnapColour(ManiaColourCode colourCode, int snap) + private void UpdateSnapColour(bool colourCode, int snap) { - if (colourCode == ManiaColourCode.On) + if (colourCode) { Colour = BindableBeatDivisor.GetColourFor(Snap, colours); } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 850d4800cd..94830adc33 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; [Cached] - protected readonly Bindable configColourCode = new Bindable(); + protected readonly Bindable configColourCode = new Bindable(); public ScrollVisualisationMethod ScrollMethod { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs b/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs deleted file mode 100644 index 91b637b71d..0000000000 --- a/osu.Game.Rulesets.Mania/UI/ManiaColourCode.cs +++ /dev/null @@ -1,11 +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.Mania.UI -{ - public enum ManiaColourCode - { - Off, - On - } -} From 91bf0d422d88fe519834cbe01771a20cc5a379d8 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 20:40:30 +0800 Subject: [PATCH 182/400] Rename ColourCode to ColourCodedNotes --- .../Configuration/ManiaRulesetConfigManager.cs | 4 ++-- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 2 +- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index f37937a736..87a4689689 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Configuration SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); - SetDefault(ManiaRulesetSetting.ColourCode, false); + SetDefault(ManiaRulesetSetting.ColourCodedNotes, false); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings @@ -36,6 +36,6 @@ namespace osu.Game.Rulesets.Mania.Configuration { ScrollTime, ScrollDirection, - ColourCode + ColourCodedNotes } } diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index c0c587f67c..552c793096 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania new SettingsCheckbox { LabelText = "Colour-coded notes", - Current = config.GetBindable(ManiaRulesetSetting.ColourCode), + Current = config.GetBindable(ManiaRulesetSetting.ColourCodedNotes), } }; } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 94830adc33..60e9613b71 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); - Config.BindWith(ManiaRulesetSetting.ColourCode, configColourCode); + Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCode); } protected override void AdjustScrollSpeed(int amount) From 3103fd8343efa3d811605ffae382da0734097dd2 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 20:54:05 +0800 Subject: [PATCH 183/400] Move snapping logic into SnapFinder --- .../Objects/Drawables/DrawableNote.cs | 68 +---------------- .../UI/DrawableManiaRuleset.cs | 7 +- osu.Game.Rulesets.Mania/Utils/SnapFinder.cs | 75 +++++++++++++++++++ 3 files changed, 83 insertions(+), 67 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Utils/SnapFinder.cs diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 7cda70f2af..998d9e625e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -7,12 +7,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; -using osu.Framework.Utils; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Skinning.Default; -using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mania.Utils; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; @@ -31,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private Bindable configColourCode { get; set; } - [Resolved(canBeNull: true)] - private ManiaBeatmap beatmap { get; set; } + [Resolved] + private SnapFinder snapFinder { get; set; } protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; @@ -63,7 +60,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.LoadComplete(); - HitObject.StartTimeBindable.BindValueChanged(_ => SnapToBeatmap(), true); + HitObject.StartTimeBindable.BindValueChanged(_ => Snap = snapFinder.FindSnap(HitObject), true); SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCode.Value, snap.NewValue), true); configColourCode.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, Snap)); @@ -108,63 +105,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public virtual void OnReleased(ManiaAction action) { } - private void SnapToBeatmap() - { - if (beatmap != null) - { - TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime); - int timeSignature = (int)currentTimingPoint.TimeSignature; - double startTime = currentTimingPoint.Time; - double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; - - double offset = startTime % secondsPerFourCounts; - double snapResult = HitObject.StartTime % secondsPerFourCounts - offset; - - if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) - { - Snap = 1; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) - { - Snap = 2; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) - { - Snap = 3; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) - { - Snap = 4; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) - { - Snap = 6; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) - { - Snap = 8; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) - { - Snap = 12; - } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) - { - Snap = 16; - } - else - { - Snap = 0; - } - } - } - - private const double LENIENCY_MS = 1.0; - private static bool AlmostDivisibleBy(double dividend, double divisor) - { - double remainder = Math.Abs(dividend) % divisor; - return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); - } private void UpdateSnapColour(bool colourCode, int snap) { diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 60e9613b71..99fb32f81f 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mania.Utils; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -45,8 +46,8 @@ namespace osu.Game.Rulesets.Mania.UI public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - [Cached(typeof(ManiaBeatmap))] - private ManiaBeatmap CachedBeatmap { get; } + [Cached] + private SnapFinder snapFinder { get; set; } public IEnumerable BarLines; @@ -84,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.UI { BarLines = new BarLineGenerator(Beatmap).BarLines; - CachedBeatmap = Beatmap; + snapFinder = new SnapFinder(Beatmap); } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs new file mode 100644 index 0000000000..01a9339552 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Utils/SnapFinder.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.Utils; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Utils +{ + public class SnapFinder + { + private ManiaBeatmap beatmap; + public SnapFinder(ManiaBeatmap beatmap) + { + this.beatmap = beatmap; + } + + public int FindSnap(HitObject hitObject) + { + TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); + int timeSignature = (int)currentTimingPoint.TimeSignature; + double startTime = currentTimingPoint.Time; + double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; + + double offset = startTime % secondsPerFourCounts; + double snapResult = hitObject.StartTime % secondsPerFourCounts - offset; + + if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) + { + return 1; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) + { + return 2; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) + { + return 3; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) + { + return 4; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) + { + return 6; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) + { + return 8; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) + { + return 12; + } + else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) + { + return 16; + } + else + { + return 0; + } + } + + private const double LENIENCY_MS = 1.0; + private static bool AlmostDivisibleBy(double dividend, double divisor) + { + double remainder = Math.Abs(dividend) % divisor; + return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); + } + } +} From 8b01082cbb3c257bd69ba6fb95cd173bce79f854 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 21:07:17 +0800 Subject: [PATCH 184/400] Fix visual tests missing dependency for ColourCodedNotes --- .../Editor/ManiaPlacementBlueprintTestScene.cs | 12 ++++++++++++ .../Editor/ManiaSelectionBlueprintTestScene.cs | 12 ++++++++++++ .../ManiaInputTestScene.cs | 13 +++++++++++++ .../Skinning/ManiaSkinnableTestScene.cs | 11 +++++++++++ osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs | 11 +++++++++++ .../Objects/Drawables/DrawableNote.cs | 13 ++++++++----- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ++-- 7 files changed, 69 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index ece523e84c..8a605a7ca3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -28,6 +30,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(typeof(IScrollingInfo))] private IScrollingInfo scrollingInfo; + [Cached] + protected readonly Bindable configColourCodedNotes = new Bindable(); + protected ManiaPlacementBlueprintTestScene() { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; @@ -41,6 +46,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 176fbba921..854d0fb14e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -2,8 +2,10 @@ // 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.Timing; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -15,6 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); + [Cached] + protected readonly Bindable configColourCodedNotes = new Bindable(); + protected ManiaSelectionBlueprintTestScene() { Add(new Column(0) @@ -26,6 +31,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index 9049bb3a82..f54d90016c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -1,9 +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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests @@ -13,11 +16,21 @@ namespace osu.Game.Rulesets.Mania.Tests private readonly Container content; protected override Container Content => content ?? base.Content; + [Cached] + protected readonly Bindable configColourCodedNotes = new Bindable(); + protected ManiaInputTestScene(int keys) { base.Content.Add(content = new LocalInputManager(keys)); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + private class LocalInputManager : ManiaInputManager { public LocalInputManager(int variant) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 1d84a2dfcb..05f07bad12 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -24,6 +25,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + [Cached] + protected readonly Bindable configColourCodedNotes = new Bindable(); + protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); protected ManiaSkinnableTestScene() @@ -38,6 +42,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning }); } + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + [Test] public void TestScrollingDown() { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 706268e478..9828db7687 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; @@ -29,6 +30,16 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { + [Cached] + protected readonly Bindable configColourCodedNotes = new Bindable(); + + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + [Test] public void TestVariousNotes() { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 998d9e625e..cfae01f5f2 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private OsuColour colours { get; set; } [Resolved] - private Bindable configColourCode { get; set; } + private Bindable configColourCodedNotes { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private SnapFinder snapFinder { get; set; } protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; @@ -60,10 +60,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.LoadComplete(); - HitObject.StartTimeBindable.BindValueChanged(_ => Snap = snapFinder.FindSnap(HitObject), true); + if (snapFinder != null) + { + HitObject.StartTimeBindable.BindValueChanged(_ => Snap = snapFinder.FindSnap(HitObject), true); - SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCode.Value, snap.NewValue), true); - configColourCode.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, Snap)); + SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCodedNotes.Value, snap.NewValue), true); + configColourCodedNotes.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, Snap)); + } } protected override void OnDirectionChanged(ValueChangedEvent e) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 99fb32f81f..818d6429e4 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; [Cached] - protected readonly Bindable configColourCode = new Bindable(); + protected readonly Bindable configColourCodedNotes = new Bindable(); public ScrollVisualisationMethod ScrollMethod { @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); - Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCode); + Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); } protected override void AdjustScrollSpeed(int amount) From bedabc1ddf673aa94d7a38414cb0c710a4f9d4bb Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 22:12:07 +0800 Subject: [PATCH 185/400] Fix cake errors --- .../ManiaPlacementBlueprintTestScene.cs | 4 +-- .../ManiaSelectionBlueprintTestScene.cs | 4 +-- .../ManiaInputTestScene.cs | 4 +-- .../Skinning/ManiaSkinnableTestScene.cs | 4 +-- .../TestSceneNotes.cs | 4 +-- .../Objects/Drawables/DrawableNote.cs | 7 +++-- .../UI/DrawableManiaRuleset.cs | 4 +-- osu.Game.Rulesets.Mania/Utils/SnapFinder.cs | 27 ++++++++++--------- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index 8a605a7ca3..c04b875245 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private IScrollingInfo scrollingInfo; [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); protected ManiaPlacementBlueprintTestScene() { @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private void load(RulesetConfigCache configCache) { var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 854d0fb14e..1c11dc397e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private readonly IAdjustableClock clock = new StopwatchClock(); [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); protected ManiaSelectionBlueprintTestScene() { @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor private void load(RulesetConfigCache configCache) { var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } public ManiaPlayfield Playfield => null; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index f54d90016c..2b2db8aa96 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Tests protected override Container Content => content ?? base.Content; [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); protected ManiaInputTestScene(int keys) { @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests private void load(RulesetConfigCache configCache) { var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } private class LocalInputManager : ManiaInputManager diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 05f07bad12..941d01a1d6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning private void load(RulesetConfigCache configCache) { var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } [Test] diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 9828db7687..1d053688a2 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -31,13 +31,13 @@ namespace osu.Game.Rulesets.Mania.Tests public class TestSceneNotes : OsuTestScene { [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) { var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } [Test] diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index cfae01f5f2..04c4b06c88 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -64,8 +63,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { HitObject.StartTimeBindable.BindValueChanged(_ => Snap = snapFinder.FindSnap(HitObject), true); - SnapBindable.BindValueChanged(snap => UpdateSnapColour(configColourCodedNotes.Value, snap.NewValue), true); - configColourCodedNotes.BindValueChanged(colourCode => UpdateSnapColour(colourCode.NewValue, Snap)); + SnapBindable.BindValueChanged(snap => updateSnapColour(configColourCodedNotes.Value, snap.NewValue), true); + configColourCodedNotes.BindValueChanged(colourCode => updateSnapColour(colourCode.NewValue, Snap)); } } @@ -109,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { } - private void UpdateSnapColour(bool colourCode, int snap) + private void updateSnapColour(bool colourCode, int snap) { if (colourCode) { diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 818d6429e4..17535c9bc9 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; [Cached] - protected readonly Bindable configColourCodedNotes = new Bindable(); + protected readonly Bindable ConfigColourCodedNotes = new Bindable(); public ScrollVisualisationMethod ScrollMethod { @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); - Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } protected override void AdjustScrollSpeed(int amount) diff --git a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs index 01a9339552..83df0eb756 100644 --- a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs +++ b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs @@ -11,7 +11,8 @@ namespace osu.Game.Rulesets.Mania.Utils { public class SnapFinder { - private ManiaBeatmap beatmap; + private readonly ManiaBeatmap beatmap; + public SnapFinder(ManiaBeatmap beatmap) { this.beatmap = beatmap; @@ -20,42 +21,41 @@ namespace osu.Game.Rulesets.Mania.Utils public int FindSnap(HitObject hitObject) { TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - int timeSignature = (int)currentTimingPoint.TimeSignature; double startTime = currentTimingPoint.Time; double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; double offset = startTime % secondsPerFourCounts; double snapResult = hitObject.StartTime % secondsPerFourCounts - offset; - if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) + if (almostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) { return 1; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) { return 2; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) { return 3; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) { return 4; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) { return 6; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) { return 8; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) { return 12; } - else if (AlmostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) + else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) { return 16; } @@ -65,11 +65,12 @@ namespace osu.Game.Rulesets.Mania.Utils } } - private const double LENIENCY_MS = 1.0; - private static bool AlmostDivisibleBy(double dividend, double divisor) + private const double leniency_ms = 1.0; + + private static bool almostDivisibleBy(double dividend, double divisor) { double remainder = Math.Abs(dividend) % divisor; - return Precision.AlmostEquals(remainder, 0, LENIENCY_MS) || Precision.AlmostEquals(remainder - divisor, 0, LENIENCY_MS); + return Precision.AlmostEquals(remainder, 0, leniency_ms) || Precision.AlmostEquals(remainder - divisor, 0, leniency_ms); } } } From 46c44c576d2c27218da69f4493d61ebffd8ab895 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 18 Apr 2021 19:16:20 -0700 Subject: [PATCH 186/400] Fix beatmap info download button content not scaling on mouse down --- .../Buttons/HeaderDownloadButton.cs | 66 ++++++++----------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index cffff86a64..6d27342049 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -47,52 +47,44 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { FillFlowContainer textSprites; - AddRangeInternal(new Drawable[] + AddInternal(shakeContainer = new ShakeContainer { - shakeContainer = new ShakeContainer + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + Child = button = new HeaderButton { RelativeSizeAxes = Axes.Both }, + }); + + button.AddRange(new Drawable[] + { + new Container { - Depth = -1, + Padding = new MarginPadding { Horizontal = 10 }, RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 5, Children = new Drawable[] { - button = new HeaderButton { RelativeSizeAxes = Axes.Both }, - new Container + textSprites = new FillFlowContainer { - // cannot nest inside here due to the structure of button (putting things in its own content). - // requires framework fix. - Padding = new MarginPadding { Horizontal = 10 }, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - textSprites = new FillFlowContainer - { - Depth = -1, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - AutoSizeDuration = 500, - AutoSizeEasing = Easing.OutQuint, - Direction = FillDirection.Vertical, - }, - new SpriteIcon - { - Depth = -1, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Icon = FontAwesome.Solid.Download, - Size = new Vector2(18), - }, - } + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 500, + AutoSizeEasing = Easing.OutQuint, + Direction = FillDirection.Vertical, }, - new DownloadProgressBar(BeatmapSet.Value) + new SpriteIcon { - Depth = -2, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Icon = FontAwesome.Solid.Download, + Size = new Vector2(18), }, - }, + } + }, + new DownloadProgressBar(BeatmapSet.Value) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, }, }); From c9967f7b7486d5744aba911a144110811b76ef04 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Apr 2021 08:37:37 -0700 Subject: [PATCH 187/400] Fix button being recreated on importing state --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index a61640a02e..85ed3f8767 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -268,11 +268,13 @@ namespace osu.Game.Overlays.BeatmapSet break; case DownloadState.Downloading: - case DownloadState.Importing: // temporary to avoid showing two buttons for maps with novideo. will be fixed in new beatmap overlay design. downloadButtonsContainer.Child = new HeaderDownloadButton(BeatmapSet.Value); break; + case DownloadState.Importing: + break; + default: downloadButtonsContainer.Child = new HeaderDownloadButton(BeatmapSet.Value); if (BeatmapSet.Value.OnlineInfo.HasVideo) From eaac4fe6c7b76bfef30d37bd7f15b6eb8d0cdccc Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 06:38:15 +0800 Subject: [PATCH 188/400] Simplify FindSnap method --- osu.Game.Rulesets.Mania/Utils/SnapFinder.cs | 49 ++++----------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs index 83df0eb756..ed3bd4af05 100644 --- a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs +++ b/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs @@ -18,51 +18,20 @@ namespace osu.Game.Rulesets.Mania.Utils this.beatmap = beatmap; } + private readonly static int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; + public int FindSnap(HitObject hitObject) { TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - double startTime = currentTimingPoint.Time; - double secondsPerFourCounts = currentTimingPoint.BeatLength * 4; + double snapResult = (hitObject.StartTime - currentTimingPoint.Time) % (currentTimingPoint.BeatLength * 4); - double offset = startTime % secondsPerFourCounts; - double snapResult = hitObject.StartTime % secondsPerFourCounts - offset; + foreach (var snap in snaps) + { + if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / (double)snap)) + return snap; + } - if (almostDivisibleBy(snapResult, secondsPerFourCounts / 4.0)) - { - return 1; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 8.0)) - { - return 2; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 12.0)) - { - return 3; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 16.0)) - { - return 4; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 24.0)) - { - return 6; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 32.0)) - { - return 8; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 48.0)) - { - return 12; - } - else if (almostDivisibleBy(snapResult, secondsPerFourCounts / 64.0)) - { - return 16; - } - else - { - return 0; - } + return 0; } private const double leniency_ms = 1.0; From e0ca44c908754b2766ac5dccce2d4d93ae2dacb0 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 07:34:17 +0800 Subject: [PATCH 189/400] Move SnapFinder from mania ruleset to osu.Game --- .../Objects/Drawables/DrawableNote.cs | 2 +- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 -- .../Utils => osu.Game/Rulesets/Objects}/SnapFinder.cs | 9 ++++----- 3 files changed, 5 insertions(+), 8 deletions(-) rename {osu.Game.Rulesets.Mania/Utils => osu.Game/Rulesets/Objects}/SnapFinder.cs (85%) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 04c4b06c88..2a363606ab 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Skinning.Default; -using osu.Game.Rulesets.Mania.Utils; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 17535c9bc9..c1b61aaf31 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -20,7 +20,6 @@ using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Mania.Utils; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -84,7 +83,6 @@ namespace osu.Game.Rulesets.Mania.UI : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; - snapFinder = new SnapFinder(Beatmap); } diff --git a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs b/osu.Game/Rulesets/Objects/SnapFinder.cs similarity index 85% rename from osu.Game.Rulesets.Mania/Utils/SnapFinder.cs rename to osu.Game/Rulesets/Objects/SnapFinder.cs index ed3bd4af05..eb8f4110a2 100644 --- a/osu.Game.Rulesets.Mania/Utils/SnapFinder.cs +++ b/osu.Game/Rulesets/Objects/SnapFinder.cs @@ -3,17 +3,16 @@ using System; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Objects; -namespace osu.Game.Rulesets.Mania.Utils +namespace osu.Game.Rulesets.Objects { public class SnapFinder { - private readonly ManiaBeatmap beatmap; + private readonly IBeatmap beatmap; - public SnapFinder(ManiaBeatmap beatmap) + public SnapFinder(IBeatmap beatmap) { this.beatmap = beatmap; } From d3db19c3ce9e0b8992be15684d9ffe3214965246 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 07:44:26 +0800 Subject: [PATCH 190/400] Simplify DrawableNote --- .../Objects/Drawables/DrawableNote.cs | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 2a363606ab..c364b395e1 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Skinning.Default; @@ -34,13 +35,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Drawable headPiece; - public readonly Bindable SnapBindable = new Bindable(); - - public int Snap - { - get => SnapBindable.Value; - set => SnapBindable.Value = value; - } + private readonly Bindable Snap = new Bindable(); public DrawableNote(Note hitObject) : base(hitObject) @@ -61,10 +56,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (snapFinder != null) { - HitObject.StartTimeBindable.BindValueChanged(_ => Snap = snapFinder.FindSnap(HitObject), true); + HitObject.StartTimeBindable.BindValueChanged(_ => Snap.Value = snapFinder.FindSnap(HitObject), true); - SnapBindable.BindValueChanged(snap => updateSnapColour(configColourCodedNotes.Value, snap.NewValue), true); - configColourCodedNotes.BindValueChanged(colourCode => updateSnapColour(colourCode.NewValue, Snap)); + Snap.BindValueChanged(_ => updateSnapColour(), true); + configColourCodedNotes.BindValueChanged(_ => updateSnapColour()); } } @@ -106,18 +101,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public virtual void OnReleased(ManiaAction action) { - } + } - private void updateSnapColour(bool colourCode, int snap) + private void updateSnapColour() { - if (colourCode) - { - Colour = BindableBeatDivisor.GetColourFor(Snap, colours); - } - else - { - Colour = Colour4.White; - } + Colour = configColourCodedNotes.Value + ? (ColourInfo)BindableBeatDivisor.GetColourFor(Snap.Value, colours) + : (ColourInfo)Colour4.White; } } } \ No newline at end of file From 8b9d2a6cff41601d51f4a184ee08a5869479ffb7 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 08:32:21 +0800 Subject: [PATCH 191/400] Remove caching for ConfigColourCodedNotes --- .../Editor/ManiaPlacementBlueprintTestScene.cs | 12 ------------ .../Editor/ManiaSelectionBlueprintTestScene.cs | 12 ------------ .../ManiaInputTestScene.cs | 13 ------------- .../Skinning/ManiaSkinnableTestScene.cs | 11 ----------- osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs | 11 ----------- .../Objects/Drawables/DrawableNote.cs | 9 +++++++-- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 5 ----- 7 files changed, 7 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index c04b875245..ece523e84c 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -4,12 +4,10 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -30,9 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(typeof(IScrollingInfo))] private IScrollingInfo scrollingInfo; - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - protected ManiaPlacementBlueprintTestScene() { scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo; @@ -46,13 +41,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); - } - protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 1c11dc397e..176fbba921 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -2,10 +2,8 @@ // 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.Timing; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -17,9 +15,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - protected ManiaSelectionBlueprintTestScene() { Add(new Column(0) @@ -31,13 +26,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); - } - public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index 2b2db8aa96..9049bb3a82 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -1,12 +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 osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests @@ -16,21 +13,11 @@ namespace osu.Game.Rulesets.Mania.Tests private readonly Container content; protected override Container Content => content ?? base.Content; - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - protected ManiaInputTestScene(int keys) { base.Content.Add(content = new LocalInputManager(keys)); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); - } - private class LocalInputManager : ManiaInputManager { public LocalInputManager(int variant) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 941d01a1d6..1d84a2dfcb 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -25,9 +24,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); protected ManiaSkinnableTestScene() @@ -42,13 +38,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning }); } - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); - } - [Test] public void TestScrollingDown() { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 1d053688a2..706268e478 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -15,7 +15,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; @@ -30,16 +29,6 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - - [BackgroundDependencyLoader] - private void load(RulesetConfigCache configCache) - { - var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); - } - [Test] public void TestVariousNotes() { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index c364b395e1..09dd0da5ac 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Input.Bindings; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -25,8 +26,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private OsuColour colours { get; set; } - [Resolved] - private Bindable configColourCodedNotes { get; set; } + [Resolved(canBeNull: true)] + private ManiaRulesetConfigManager config { get; set; } + + private readonly Bindable configColourCodedNotes = new Bindable(); [Resolved(canBeNull: true)] private SnapFinder snapFinder { get; set; } @@ -56,6 +59,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (snapFinder != null) { + config?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + HitObject.StartTimeBindable.BindValueChanged(_ => Snap.Value = snapFinder.FindSnap(HitObject), true); Snap.BindValueChanged(_ => updateSnapColour(), true); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index c1b61aaf31..174727cc2d 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -54,9 +54,6 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; - [Cached] - protected readonly Bindable ConfigColourCodedNotes = new Bindable(); - public ScrollVisualisationMethod ScrollMethod { get => scrollMethod; @@ -111,8 +108,6 @@ namespace osu.Game.Rulesets.Mania.UI configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange); - - Config.BindWith(ManiaRulesetSetting.ColourCodedNotes, ConfigColourCodedNotes); } protected override void AdjustScrollSpeed(int amount) From 1f48378ce72b4036601eb09d0a6090ba51434d38 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 08:53:45 +0800 Subject: [PATCH 192/400] Add xmldoc to SnapFinder --- osu.Game/Rulesets/Objects/SnapFinder.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Objects/SnapFinder.cs b/osu.Game/Rulesets/Objects/SnapFinder.cs index eb8f4110a2..f915d41305 100644 --- a/osu.Game/Rulesets/Objects/SnapFinder.cs +++ b/osu.Game/Rulesets/Objects/SnapFinder.cs @@ -8,10 +8,17 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects { + /// + /// Used to find the lowest beat divisor that a aligns to in an + /// public class SnapFinder { private readonly IBeatmap beatmap; + /// + /// Creates a new SnapFinder instance. + /// + /// The beatmap to align to when evaulating. public SnapFinder(IBeatmap beatmap) { this.beatmap = beatmap; @@ -19,6 +26,10 @@ namespace osu.Game.Rulesets.Objects private readonly static int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; + /// + /// Finds the lowest beat divisor that the given HitObject aligns to. + /// + /// The to evaluate. public int FindSnap(HitObject hitObject) { TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); From 211bff6a8f09d8379b1574dfb80d97b76e318ae3 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sun, 25 Apr 2021 09:21:25 +0800 Subject: [PATCH 193/400] Fix cake errors --- .../Objects/Drawables/DrawableNote.cs | 10 +++++----- osu.Game/Rulesets/Objects/SnapFinder.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 09dd0da5ac..9f30221fde 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Drawable headPiece; - private readonly Bindable Snap = new Bindable(); + private readonly Bindable snap = new Bindable(); public DrawableNote(Note hitObject) : base(hitObject) @@ -61,9 +61,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { config?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); - HitObject.StartTimeBindable.BindValueChanged(_ => Snap.Value = snapFinder.FindSnap(HitObject), true); + HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = snapFinder.FindSnap(HitObject), true); - Snap.BindValueChanged(_ => updateSnapColour(), true); + snap.BindValueChanged(_ => updateSnapColour(), true); configColourCodedNotes.BindValueChanged(_ => updateSnapColour()); } } @@ -106,12 +106,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public virtual void OnReleased(ManiaAction action) { - } + } private void updateSnapColour() { Colour = configColourCodedNotes.Value - ? (ColourInfo)BindableBeatDivisor.GetColourFor(Snap.Value, colours) + ? (ColourInfo)BindableBeatDivisor.GetColourFor(snap.Value, colours) : (ColourInfo)Colour4.White; } } diff --git a/osu.Game/Rulesets/Objects/SnapFinder.cs b/osu.Game/Rulesets/Objects/SnapFinder.cs index f915d41305..3bc1d5cfb7 100644 --- a/osu.Game/Rulesets/Objects/SnapFinder.cs +++ b/osu.Game/Rulesets/Objects/SnapFinder.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Objects this.beatmap = beatmap; } - private readonly static int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; + private static readonly int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; /// /// Finds the lowest beat divisor that the given HitObject aligns to. @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects foreach (var snap in snaps) { - if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / (double)snap)) + if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / snap)) return snap; } From 6fd77e536df6bfcab9a3c2ab8d8c19fdc822fb8b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 25 Apr 2021 05:34:54 +0200 Subject: [PATCH 194/400] Add unsnap check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 5 +- osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs | 119 ++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index f33feac971..aa3459a01a 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -22,7 +22,10 @@ namespace osu.Game.Rulesets.Edit // Audio new CheckAudioPresence(), - new CheckAudioQuality() + new CheckAudioQuality(), + + // Compose + new CheckUnsnaps() }; public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs new file mode 100644 index 0000000000..835c4bdb69 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs @@ -0,0 +1,119 @@ +// 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.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckUnsnaps : ICheck + { + private const double unsnap_ms_threshold = 2; + + private static readonly int[] greatest_common_divisors = { 16, 12, 9, 7, 5 }; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Unsnapped hitobjects"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplate2MsOrMore(this), + new IssueTemplate1MsOrMore(this) + }; + + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + { + foreach (var hitobject in playableBeatmap.HitObjects) + { + double startUnsnap = hitobject.StartTime - closestSnapTime(playableBeatmap, hitobject.StartTime); + string startPostfix = hitobject is IHasDuration ? "start" : ""; + foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix)) + yield return issue; + + if (hitobject is IHasRepeats hasRepeats) + { + for (int repeatIndex = 0; repeatIndex < hasRepeats.RepeatCount; ++repeatIndex) + { + double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1); + double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1); + double repeatUnsnap = repeatTime - closestSnapTime(playableBeatmap, repeatTime); + foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat")) + yield return issue; + } + } + + if (hitobject is IHasDuration hasDuration) + { + double endUnsnap = hasDuration.EndTime - closestSnapTime(playableBeatmap, hasDuration.EndTime); + foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end")) + yield return issue; + } + } + } + + private IEnumerable getUnsnapIssues(HitObject hitobject, double unsnap, double time, string postfix = "") + { + if (Math.Abs(unsnap) >= unsnap_ms_threshold) + yield return new IssueTemplate2MsOrMore(this).Create(hitobject, unsnap, time, postfix); + else if (Math.Abs(unsnap) >= 1) + yield return new IssueTemplate1MsOrMore(this).Create(hitobject, unsnap, time, postfix); + + // We don't care about unsnaps < 1 ms, as all object ends have these due to the way SV works. + } + + private int closestSnapTime(IBeatmap playableBeatmap, double time) + { + var timingPoint = playableBeatmap.ControlPointInfo.TimingPointAt(time); + double smallestUnsnap = greatest_common_divisors.Select(divisor => Math.Abs(time - snapTime(timingPoint, time, divisor))).Min(); + + return (int)Math.Round(time + smallestUnsnap); + } + + private int snapTime(TimingControlPoint timingPoint, double time, int beatDivisor) + { + double beatLength = timingPoint.BeatLength / beatDivisor; + int beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + + // Casting to int matches the editor in both stable and lazer. + return (int)(timingPoint.Time + beatLengths * beatLength); + } + + public abstract class IssueTemplateUnsnap : IssueTemplate + { + protected IssueTemplateUnsnap(ICheck check, IssueType type) + : base(check, type, "{0:0.##} is unsnapped by {1:0.##} ms.") + { + } + + public Issue Create(HitObject hitobject, double unsnap, double time, string postfix = "") + { + string objectName = hitobject.GetType().Name; + if (!string.IsNullOrEmpty(postfix)) + objectName += " " + postfix; + + return new Issue(hitobject, this, objectName, unsnap) { Time = time }; + } + } + + public class IssueTemplate2MsOrMore : IssueTemplateUnsnap + { + public IssueTemplate2MsOrMore(ICheck check) + : base(check, IssueType.Problem) + { + } + } + + public class IssueTemplate1MsOrMore : IssueTemplateUnsnap + { + public IssueTemplate1MsOrMore(ICheck check) + : base(check, IssueType.Negligible) + { + } + } + } +} From fa8e8ed36f3b410fc91eb07a49bcb4413e52fbce Mon Sep 17 00:00:00 2001 From: plan-do-break-fix Date: Sat, 24 Apr 2021 22:57:18 -0500 Subject: [PATCH 195/400] fix(docs): corrects typo in project README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c539f9f4d8..eb790ca18f 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commo 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 passes 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. +**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 passes come at the end of development, preceded 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: From f9e228d6bfb4e94ded823da5ceb6434c09db6b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Apr 2021 17:40:23 +0200 Subject: [PATCH 196/400] Use null-permitting BDL to reduce number of fields --- .../Objects/Drawables/DrawableNote.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 9f30221fde..52e343ba25 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -26,14 +26,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private OsuColour colours { get; set; } - [Resolved(canBeNull: true)] - private ManiaRulesetConfigManager config { get; set; } - private readonly Bindable configColourCodedNotes = new Bindable(); - [Resolved(canBeNull: true)] - private SnapFinder snapFinder { get; set; } - protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; private readonly Drawable headPiece; @@ -53,13 +47,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } - protected override void LoadComplete() + [BackgroundDependencyLoader(true)] + private void load(ManiaRulesetConfigManager rulesetConfig, SnapFinder snapFinder) { - base.LoadComplete(); - if (snapFinder != null) { - config?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + rulesetConfig?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = snapFinder.FindSnap(HitObject), true); @@ -115,4 +108,4 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables : (ColourInfo)Colour4.White; } } -} \ No newline at end of file +} From afb67726f0057525b975bbd7b51d6e156e3316a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Apr 2021 17:40:49 +0200 Subject: [PATCH 197/400] Reduce casting --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 52e343ba25..a5749408af 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Configuration; @@ -15,6 +14,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osu.Game.Skinning; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -104,8 +104,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private void updateSnapColour() { Colour = configColourCodedNotes.Value - ? (ColourInfo)BindableBeatDivisor.GetColourFor(snap.Value, colours) - : (ColourInfo)Colour4.White; + ? BindableBeatDivisor.GetColourFor(snap.Value, colours) + : Color4.White; } } } From e14255f39548b250d5c276663f4765cbc41d2596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Apr 2021 17:42:56 +0200 Subject: [PATCH 198/400] Rename {Snap -> BeatDivisor}Finder --- .../Objects/Drawables/DrawableNote.cs | 6 +++--- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ++-- .../{SnapFinder.cs => BeatDivisorFinder.cs} | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game/Rulesets/Objects/{SnapFinder.cs => BeatDivisorFinder.cs} (77%) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index a5749408af..2d2fba0ad5 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -48,13 +48,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } [BackgroundDependencyLoader(true)] - private void load(ManiaRulesetConfigManager rulesetConfig, SnapFinder snapFinder) + private void load(ManiaRulesetConfigManager rulesetConfig, BeatDivisorFinder beatDivisorFinder) { - if (snapFinder != null) + if (beatDivisorFinder != null) { rulesetConfig?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); - HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = snapFinder.FindSnap(HitObject), true); + HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = beatDivisorFinder.FindDivisor(HitObject), true); snap.BindValueChanged(_ => updateSnapColour(), true); configColourCodedNotes.BindValueChanged(_ => updateSnapColour()); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 174727cc2d..0177c01240 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.UI public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; [Cached] - private SnapFinder snapFinder { get; set; } + private BeatDivisorFinder beatDivisorFinder { get; set; } public IEnumerable BarLines; @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.UI : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; - snapFinder = new SnapFinder(Beatmap); + beatDivisorFinder = new BeatDivisorFinder(Beatmap); } [BackgroundDependencyLoader] diff --git a/osu.Game/Rulesets/Objects/SnapFinder.cs b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs similarity index 77% rename from osu.Game/Rulesets/Objects/SnapFinder.cs rename to osu.Game/Rulesets/Objects/BeatDivisorFinder.cs index 3bc1d5cfb7..29b271a7e7 100644 --- a/osu.Game/Rulesets/Objects/SnapFinder.cs +++ b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs @@ -9,17 +9,17 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects { /// - /// Used to find the lowest beat divisor that a aligns to in an + /// Used to find the lowest beat divisor that a aligns to in an . /// - public class SnapFinder + public class BeatDivisorFinder { private readonly IBeatmap beatmap; /// - /// Creates a new SnapFinder instance. + /// Creates a new instance. /// - /// The beatmap to align to when evaulating. - public SnapFinder(IBeatmap beatmap) + /// The beatmap to use when calculating beat divisor alignment. + public BeatDivisorFinder(IBeatmap beatmap) { this.beatmap = beatmap; } @@ -27,10 +27,10 @@ namespace osu.Game.Rulesets.Objects private static readonly int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; /// - /// Finds the lowest beat divisor that the given HitObject aligns to. + /// Finds the lowest beat divisor that the given aligns to. /// /// The to evaluate. - public int FindSnap(HitObject hitObject) + public int FindDivisor(HitObject hitObject) { TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); double snapResult = (hitObject.StartTime - currentTimingPoint.Time) % (currentTimingPoint.BeatLength * 4); From 33a5c156a1e79e3ffe22f7199e9348f8f42b2464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Apr 2021 17:52:11 +0200 Subject: [PATCH 199/400] Use existing snap list from BindableBeatDivisor --- osu.Game/Rulesets/Objects/BeatDivisorFinder.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs index 29b271a7e7..63a4ff2d34 100644 --- a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs +++ b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Objects { @@ -24,8 +25,6 @@ namespace osu.Game.Rulesets.Objects this.beatmap = beatmap; } - private static readonly int[] snaps = { 1, 2, 3, 4, 6, 8, 12, 16 }; - /// /// Finds the lowest beat divisor that the given aligns to. /// @@ -35,10 +34,10 @@ namespace osu.Game.Rulesets.Objects TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); double snapResult = (hitObject.StartTime - currentTimingPoint.Time) % (currentTimingPoint.BeatLength * 4); - foreach (var snap in snaps) + foreach (var divisor in BindableBeatDivisor.VALID_DIVISORS) { - if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / snap)) - return snap; + if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / divisor)) + return divisor; } return 0; From 8bb1fcd39b29b55b693d9540777f96ecb0d8d3c6 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Mon, 26 Apr 2021 07:07:32 +0800 Subject: [PATCH 200/400] Add tests for BeatDivisorFinder --- osu.Game.Tests/NonVisual/BeatDivisorFinder.cs | 116 ++++++++++++++++++ .../Rulesets/Objects/BeatDivisorFinder.cs | 3 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/NonVisual/BeatDivisorFinder.cs diff --git a/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs b/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs new file mode 100644 index 0000000000..742f5790d6 --- /dev/null +++ b/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs @@ -0,0 +1,116 @@ +// 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.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Tests.NonVisual +{ + public class BeatDivisorFinderTest + { + [Test] + public void TestFindDivisor() + { + const int beatLength = 1000; + + var beatmap = new Beatmap + { + HitObjects = new List + { + new HitObject { StartTime = -beatLength / 3 }, + new HitObject { StartTime = 0 }, + new HitObject { StartTime = beatLength / 16 }, + new HitObject { StartTime = beatLength / 12 }, + new HitObject { StartTime = beatLength / 8 }, + new HitObject { StartTime = beatLength / 6 }, + new HitObject { StartTime = beatLength / 4 }, + new HitObject { StartTime = beatLength / 3 }, + new HitObject { StartTime = beatLength / 2 }, + new HitObject { StartTime = beatLength }, + new HitObject { StartTime = beatLength + beatLength / 7 } + }, + ControlPointInfo = new ControlPointInfo() + }; + + beatmap.ControlPointInfo.Add(0, new TimingControlPoint() + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = beatLength + }); + + var beatDivisorFinder = new BeatDivisorFinder(beatmap); + + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[0]), 3); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[1]), 1); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[2]), 16); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[3]), 12); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[4]), 8); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[5]), 6); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[6]), 4); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[7]), 3); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[8]), 2); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[9]), 1); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[10]), 0); + } + + [Test] + public void TestFindDivisorWithTempoChanges() + { + const int firstBeatLength = 1000; + const int secondBeatLength = 700; + const int thirdBeatLength = 200; + + const int firstBeatLengthStart = 0; + const int secondBeatLengthStart = 1000; + const int thirdBeatLengthStart = 2000; + + var beatmap = new Beatmap + { + HitObjects = new List + { + new HitObject { StartTime = firstBeatLengthStart }, + new HitObject { StartTime = firstBeatLengthStart + firstBeatLength / 2 }, + new HitObject { StartTime = secondBeatLengthStart }, + new HitObject { StartTime = secondBeatLengthStart + secondBeatLength / 2 }, + new HitObject { StartTime = thirdBeatLengthStart }, + new HitObject { StartTime = thirdBeatLengthStart + thirdBeatLength / 2 }, + }, + ControlPointInfo = new ControlPointInfo() + }; + + var firstTimingControlPoint = new TimingControlPoint() + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = firstBeatLength + }; + + var secondTimingControlPoint = new TimingControlPoint() + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = secondBeatLength + }; + + var thirdTimingControlPoint = new TimingControlPoint() + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = thirdBeatLength + }; + + beatmap.ControlPointInfo.Add(firstBeatLengthStart, firstTimingControlPoint); + beatmap.ControlPointInfo.Add(secondBeatLengthStart, secondTimingControlPoint); + beatmap.ControlPointInfo.Add(thirdBeatLengthStart, thirdTimingControlPoint); + + var beatDivisorFinder = new BeatDivisorFinder(beatmap); + + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[0]), 1); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[1]), 2); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[2]), 1); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[3]), 2); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[4]), 1); + Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[5]), 2); + } + } +} diff --git a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs index 63a4ff2d34..1479b22942 100644 --- a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs +++ b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs @@ -27,12 +27,13 @@ namespace osu.Game.Rulesets.Objects /// /// Finds the lowest beat divisor that the given aligns to. + /// Returns 0 if it does not align to any divisor. /// /// The to evaluate. public int FindDivisor(HitObject hitObject) { TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - double snapResult = (hitObject.StartTime - currentTimingPoint.Time) % (currentTimingPoint.BeatLength * 4); + double snapResult = hitObject.StartTime - currentTimingPoint.Time; foreach (var divisor in BindableBeatDivisor.VALID_DIVISORS) { From 0b9172a1dc08dfa3e432b93aa48d0a3b7862fd31 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 26 Apr 2021 02:39:18 +0300 Subject: [PATCH 201/400] Animate back slider repeat and tail circle pieces --- .../Objects/Drawables/DrawableSliderRepeat.cs | 7 +++++-- .../Objects/Drawables/DrawableSliderTail.cs | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 76490e0de1..5af2a38559 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -26,7 +26,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; - public Drawable CirclePiece { get; private set; } + public SkinnableDrawable CirclePiece { get; private set; } + private Drawable scaleContainer; private ReverseArrowPiece arrow; @@ -53,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Children = new[] + Children = new Drawable[] { // no default for this; only visible in legacy skins. CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), @@ -91,6 +92,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateHitStateTransforms(state); + (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); + switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 87f098dd29..84b9b881a2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -84,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Debug.Assert(HitObject.HitWindows != null); + (circlePiece.Drawable as IMainCirclePiece)?.Animate(state); + switch (state) { case ArmedState.Idle: From e6474e6ff73b968970dc0c9e943130a6f865af06 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 11:47:38 +0900 Subject: [PATCH 202/400] Remove redundant statement (lifetime is set in base) --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 312ed93e45..6ab6a4b984 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -212,10 +212,7 @@ namespace osu.Game.Rulesets.Objects.Drawables // LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset. // We override this with DHO's InitialLifetimeOffset for a non-pooled DHO. if (entry is SyntheticHitObjectEntry) - entry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; - - LifetimeStart = entry.LifetimeStart; - LifetimeEnd = entry.LifetimeEnd; + LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; ensureEntryHasResult(); From 9178aa1d7d745f3057f0080ec704e86ced17f534 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 04:48:56 +0200 Subject: [PATCH 203/400] Add unsnap check tests --- .../Editing/Checks/CheckUnsnapsTest.cs | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs new file mode 100644 index 0000000000..88939c43ce --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs @@ -0,0 +1,160 @@ +// 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 Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckUnsnapsTest + { + private CheckUnsnaps check; + private ControlPointInfo cpi; + + [SetUp] + public void Setup() + { + check = new CheckUnsnaps(); + + cpi = new ControlPointInfo(); + cpi.Add(100, new TimingControlPoint { BeatLength = 100 }); + } + + [Test] + public void TestCircleSnapped() + { + assertOk(new List + { + new HitCircle { StartTime = 100 } + }); + } + + [Test] + public void TestCircleUnsnapped1Ms() + { + assert1Ms(new List + { + new HitCircle { StartTime = 101 } + }); + + assert1Ms(new List + { + new HitCircle { StartTime = 99 } + }); + } + + [Test] + public void TestCircleUnsnapped2Ms() + { + assert2Ms(new List + { + new HitCircle { StartTime = 102 } + }); + + assert2Ms(new List + { + new HitCircle { StartTime = 98 } + }); + } + + [Test] + public void TestSliderSnapped() + { + // Slider ends are naturally < 1 ms unsnapped because of how SV works. + var mockSlider = new Mock(); + mockSlider.SetupGet(s => s.StartTime).Returns(100); + mockSlider.As().Setup(r => r.RepeatCount).Returns(0); + mockSlider.As().Setup(d => d.Duration).Returns(400.75d); + + assertOk(new List + { + mockSlider.Object + }); + } + + [Test] + public void TestSliderUnsnapped1Ms() + { + assert1Ms(new List + { + getSliderMock(startTime: 101, endTime: 401.75d).Object + }, count: 2); + + // End is only off by 0.25 ms, hence count 1. + assert1Ms(new List + { + getSliderMock(startTime: 99, endTime: 399.75d).Object + }, count: 1); + } + + [Test] + public void TestSliderUnsnapped2Ms() + { + assert2Ms(new List + { + getSliderMock(startTime: 102, endTime: 402.75d).Object + }, count: 2); + + // Start and end are 2 ms and 1.25 ms off respectively, hence two different issues in one object. + var hitobjects = new List + { + getSliderMock(startTime: 98, endTime: 398.75d).Object + }; + + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(2)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnaps.IssueTemplate1MsOrMore)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnaps.IssueTemplate2MsOrMore)); + } + + private Mock getSliderMock(double startTime, double endTime, int repeats = 0) + { + var mockSlider = new Mock(); + mockSlider.SetupGet(s => s.StartTime).Returns(startTime); + mockSlider.As().Setup(r => r.RepeatCount).Returns(repeats); + mockSlider.As().Setup(d => d.EndTime).Returns(endTime); + + return mockSlider; + } + + private void assertOk(List hitobjects) + { + Assert.That(check.Run(getPlayableBeatmap(hitobjects), null), Is.Empty); + } + + private void assert1Ms(List hitobjects, int count = 1) + { + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnaps.IssueTemplate1MsOrMore)); + } + + private void assert2Ms(List hitobjects, int count = 1) + { + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnaps.IssueTemplate2MsOrMore)); + } + + private IBeatmap getPlayableBeatmap(List hitobjects) + { + return new Beatmap + { + ControlPointInfo = cpi, + HitObjects = hitobjects + }; + } + } +} From 20e3cadd30e6c2e9e49b0f880915646e7caa9624 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 12:04:59 +0900 Subject: [PATCH 204/400] freeIfInUse -> free, and add comments --- .../Rulesets/Objects/Pooling/DrawableObject.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs index b29e6a6c3c..27ed4c04f2 100644 --- a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs +++ b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs @@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.Objects.Pooling { base.LoadAsyncComplete(); + // Apply the initial entry given in the constructor. if (Entry != null && !HasEntryApplied) Apply(Entry); } @@ -60,7 +61,8 @@ namespace osu.Game.Rulesets.Objects.Pooling /// public void Apply(TEntry entry) { - freeIfInUse(); + if (HasEntryApplied) + free(); setLifetime(entry.LifetimeStart, entry.LifetimeEnd); Entry = entry; @@ -74,8 +76,9 @@ namespace osu.Game.Rulesets.Objects.Pooling { base.FreeAfterUse(); - if (IsInPool) - freeIfInUse(); + // We preserve the existing entry in case we want to move a non-pooled drawable between different parent drawables. + if (HasEntryApplied && IsInPool) + free(); } /// @@ -104,11 +107,9 @@ namespace osu.Game.Rulesets.Objects.Pooling } } - private void freeIfInUse() + private void free() { - if (!HasEntryApplied) return; - - Debug.Assert(Entry != null); + Debug.Assert(Entry != null && HasEntryApplied); OnFree(Entry); From 6561a7c7d697dafd15d3f35b82d7ccd86552ca0b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 12:06:21 +0900 Subject: [PATCH 205/400] Rename DrawableObject -> PoolableDrawableWithLifetime --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- ...{DrawableObject.cs => PoolableDrawableWithLifetime.cs} | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Rulesets/Objects/Pooling/{DrawableObject.cs => PoolableDrawableWithLifetime.cs} (92%) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 6ab6a4b984..7739994527 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -26,7 +26,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { [Cached(typeof(DrawableHitObject))] - public abstract class DrawableHitObject : DrawableObject + public abstract class DrawableHitObject : PoolableDrawableWithLifetime { /// /// Invoked after this 's applied has had its defaults applied. @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// /// The to be initially applied to this . - /// If null, a hitobject is expected to be later applied via (or automatically via pooling). + /// If null, a hitobject is expected to be later applied via (or automatically via pooling). /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) : base(initialHitObject != null ? new SyntheticHitObjectEntry(initialHitObject) : null) diff --git a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs similarity index 92% rename from osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs rename to osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 27ed4c04f2..93e476be76 100644 --- a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -13,15 +13,15 @@ namespace osu.Game.Rulesets.Objects.Pooling /// A that is controlled by to implement drawable pooling and replay rewinding. /// /// The type storing state and controlling this drawable. - public abstract class DrawableObject : PoolableDrawable where TEntry : LifetimeEntry + public abstract class PoolableDrawableWithLifetime : PoolableDrawable where TEntry : LifetimeEntry { /// - /// The entry holding essential state of this . + /// The entry holding essential state of this . /// protected TEntry? Entry { get; private set; } /// - /// Whether is applied to this . + /// Whether is applied to this . /// When an initial entry is specified in the constructor, is set but not applied until loading is completed. /// protected bool HasEntryApplied { get; private set; } @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Objects.Pooling public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; - protected DrawableObject(TEntry? initialEntry = null) + protected PoolableDrawableWithLifetime(TEntry? initialEntry = null) { Entry = initialEntry; } From 049e42fa854f2ec8bb0493246d34a8d43cb9209a Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 05:07:24 +0200 Subject: [PATCH 206/400] Move snapping responsibility to `IBeatmap` Seems `EditorBeatmap` already implements a different kind of `SnapTime` from `IBeatSnapProvider`, so method names here aren't great. This is very similar to what https://github.com/ppy/osu/pull/12558 is doing, so may need to do some duplicate resolution later, especially surrounding `ClosestBeatSnapDivisor`. Worth noting that this change makes 1/7, 1/5, etc unsupported for now, as we now rely on `BindableBeatDivisor.VALID_DIVISORS`. --- osu.Game/Beatmaps/Beatmap.cs | 26 ++++++++++++++++++ osu.Game/Beatmaps/IBeatmap.cs | 22 +++++++++++++++ osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs | 27 +++---------------- osu.Game/Screens/Edit/EditorBeatmap.cs | 13 +++++---- osu.Game/Screens/Play/GameplayBeatmap.cs | 9 +++++++ 5 files changed, 68 insertions(+), 29 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index e5b6a4bc44..1ce01aee24 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Game.Beatmaps.ControlPoints; using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; +using osu.Game.Screens.Edit; namespace osu.Game.Beatmaps { @@ -74,6 +75,31 @@ namespace osu.Game.Beatmaps return mostCommon.beatLength; } + public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) + { + var timingPoint = ControlPointInfo.TimingPointAt(referenceTime ?? time); + var beatLength = timingPoint.BeatLength / beatDivisor; + var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + + return (int)(timingPoint.Time + beatLengths * beatLength); + } + + public int SnapTimeAnyDivisor(double time, double? referenceTime = null) + { + return SnapTimeForDivisor(time, ClosestBeatSnapDivisor(time, referenceTime), referenceTime); + } + + public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) + { + double getUnsnap(int divisor) => Math.Abs(time - SnapTimeForDivisor(time, divisor, referenceTime)); + + int[] divisors = BindableBeatDivisor.VALID_DIVISORS; + double smallestUnsnap = divisors.Min(getUnsnap); + int closestDivisor = divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); + + return closestDivisor; + } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 769b33009a..3b043cb59b 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -51,6 +51,28 @@ namespace osu.Game.Beatmaps /// double GetMostCommonBeatLength(); + /// + /// Returns the time on the given beat divisor closest to the given time. + /// + /// The time to find the closest snapped time to. + /// The beat divisor to snap to. + /// The time at which the timing point is retrieved, by default same as time. + int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null); + + /// + /// Returns the time on any valid beat divisor closest to the given time. + /// + /// The time to find the closest snapped time to. + /// The time at which the timing point is retrieved, by default same as time. + int SnapTimeAnyDivisor(double time, double? referenceTime = null); + + /// + /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned. + /// + /// The time to find the closest beat snap divisor to. + /// The time at which the timing point is retrieved, by default same as time. + int ClosestBeatSnapDivisor(double time, double? referenceTime = null); + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs index 835c4bdb69..d15dc6f179 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -16,8 +14,6 @@ namespace osu.Game.Rulesets.Edit.Checks { private const double unsnap_ms_threshold = 2; - private static readonly int[] greatest_common_divisors = { 16, 12, 9, 7, 5 }; - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Unsnapped hitobjects"); public IEnumerable PossibleTemplates => new IssueTemplate[] @@ -30,7 +26,7 @@ namespace osu.Game.Rulesets.Edit.Checks { foreach (var hitobject in playableBeatmap.HitObjects) { - double startUnsnap = hitobject.StartTime - closestSnapTime(playableBeatmap, hitobject.StartTime); + double startUnsnap = hitobject.StartTime - playableBeatmap.SnapTimeAnyDivisor(hitobject.StartTime); string startPostfix = hitobject is IHasDuration ? "start" : ""; foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix)) yield return issue; @@ -41,7 +37,7 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1); double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1); - double repeatUnsnap = repeatTime - closestSnapTime(playableBeatmap, repeatTime); + double repeatUnsnap = repeatTime - playableBeatmap.SnapTimeAnyDivisor(repeatTime); foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat")) yield return issue; } @@ -49,7 +45,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitobject is IHasDuration hasDuration) { - double endUnsnap = hasDuration.EndTime - closestSnapTime(playableBeatmap, hasDuration.EndTime); + double endUnsnap = hasDuration.EndTime - playableBeatmap.SnapTimeAnyDivisor(hasDuration.EndTime); foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end")) yield return issue; } @@ -66,23 +62,6 @@ namespace osu.Game.Rulesets.Edit.Checks // We don't care about unsnaps < 1 ms, as all object ends have these due to the way SV works. } - private int closestSnapTime(IBeatmap playableBeatmap, double time) - { - var timingPoint = playableBeatmap.ControlPointInfo.TimingPointAt(time); - double smallestUnsnap = greatest_common_divisors.Select(divisor => Math.Abs(time - snapTime(timingPoint, time, divisor))).Min(); - - return (int)Math.Round(time + smallestUnsnap); - } - - private int snapTime(TimingControlPoint timingPoint, double time, int beatDivisor) - { - double beatLength = timingPoint.BeatLength / beatDivisor; - int beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); - - // Casting to int matches the editor in both stable and lazer. - return (int)(timingPoint.Time + beatLengths * beatLength); - } - public abstract class IssueTemplateUnsnap : IssueTemplate { protected IssueTemplateUnsnap(ICheck check, IssueType type) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 4bf4a3b8f3..9334814e2a 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -301,14 +301,17 @@ namespace osu.Game.Screens.Edit return list.Count - 1; } - public double SnapTime(double time, double? referenceTime) + public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) { - var timingPoint = ControlPointInfo.TimingPointAt(referenceTime ?? time); - var beatLength = timingPoint.BeatLength / BeatDivisor; - - return timingPoint.Time + (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero) * beatLength; + return PlayableBeatmap.SnapTimeForDivisor(time, beatDivisor, referenceTime); } + public int SnapTimeAnyDivisor(double time, double? referenceTime = null) => PlayableBeatmap.SnapTimeAnyDivisor(time, referenceTime); + + public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatSnapDivisor(time, referenceTime); + + public double SnapTime(double time, double? referenceTime) => SnapTimeForDivisor(time, BeatDivisor, referenceTime); + public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; public int BeatDivisor => beatDivisor?.Value ?? 1; diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs index 74fbe540fa..a3a2bbd41b 100644 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ b/osu.Game/Screens/Play/GameplayBeatmap.cs @@ -45,6 +45,15 @@ namespace osu.Game.Screens.Play public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); + public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) + { + return PlayableBeatmap.SnapTimeForDivisor(time, beatDivisor, referenceTime); + } + + public int SnapTimeAnyDivisor(double time, double? referenceTime = null) => PlayableBeatmap.SnapTimeAnyDivisor(time, referenceTime); + + public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatSnapDivisor(time, referenceTime); + public IBeatmap Clone() => PlayableBeatmap.Clone(); private readonly Bindable lastJudgementResult = new Bindable(); From e8d83f2f99887423d9be29ac5eea0cdb673e24b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 14:33:30 +0900 Subject: [PATCH 207/400] Rename "EditRuleset" and "EditPlayfield" to use full "Editor" keyword --- ...eManiaEditRuleset.cs => DrawableManiaEditorRuleset.cs} | 6 +++--- .../{ManiaEditPlayfield.cs => ManiaEditorPlayfield.cs} | 4 ++-- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 4 ++-- ...wableOsuEditRuleset.cs => DrawableOsuEditorRuleset.cs} | 8 ++++---- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game.Rulesets.Mania/Edit/{DrawableManiaEditRuleset.cs => DrawableManiaEditorRuleset.cs} (78%) rename osu.Game.Rulesets.Mania/Edit/{ManiaEditPlayfield.cs => ManiaEditorPlayfield.cs} (75%) rename osu.Game.Rulesets.Osu/Edit/{DrawableOsuEditRuleset.cs => DrawableOsuEditorRuleset.cs} (93%) diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs similarity index 78% rename from osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs rename to osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs index 445df79f6f..b0af8c503b 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs @@ -12,16 +12,16 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Edit { - public class DrawableManiaEditRuleset : DrawableManiaRuleset + public class DrawableManiaEditorRuleset : DrawableManiaRuleset { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public DrawableManiaEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) + public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } - protected override Playfield CreatePlayfield() => new ManiaEditPlayfield(Beatmap.Stages) + protected override Playfield CreatePlayfield() => new ManiaEditorPlayfield(Beatmap.Stages) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs similarity index 75% rename from osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs rename to osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs index a42f793a77..186d50716e 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs @@ -7,9 +7,9 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaEditPlayfield : ManiaPlayfield + public class ManiaEditorPlayfield : ManiaPlayfield { - public ManiaEditPlayfield(List stages) + public ManiaEditorPlayfield(List stages) : base(stages) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index d9570bf8be..2baec95c94 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Edit { public class ManiaHitObjectComposer : HitObjectComposer { - private DrawableManiaEditRuleset drawableRuleset; + private DrawableManiaEditorRuleset drawableRuleset; private ManiaBeatSnapGrid beatSnapGrid; private InputManager inputManager; @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.Edit protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { - drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); + drawableRuleset = new DrawableManiaEditorRuleset(ruleset, beatmap, mods); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it dependencies.CacheAs(drawableRuleset.ScrollingInfo); diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs similarity index 93% rename from osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs rename to osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index b8d0637e90..7227e2b511 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -17,18 +17,18 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit { - public class DrawableOsuEditRuleset : DrawableOsuRuleset + public class DrawableOsuEditorRuleset : DrawableOsuRuleset { - public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) + public DrawableOsuEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } - protected override Playfield CreatePlayfield() => new OsuEditPlayfield(); + protected override Playfield CreatePlayfield() => new OsuEditorPlayfield(); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One }; - private class OsuEditPlayfield : OsuPlayfield + private class OsuEditorPlayfield : OsuPlayfield { private Bindable hitAnimations; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 396fd41377..7b67d7aaf1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) - => new DrawableOsuEditRuleset(ruleset, beatmap, mods); + => new DrawableOsuEditorRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { From bda8f68da45a8645a07abd6c5fc1d7e5a2478156 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:03:43 +0900 Subject: [PATCH 208/400] Add failing test --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 2cc031405e..590d159300 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -34,6 +34,18 @@ namespace osu.Game.Rulesets.Osu.Tests private List judgementResults; + [Test] + public void TestPressBothKeysSimultaneouslyAndReleaseOne() + { + performTest(new List + { + new OsuReplayFrame { Position = Vector2.Zero, Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = Vector2.Zero, Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, + }); + + AddAssert("Tracking retained", assertMaxJudge); + } + /// /// Scenario: /// - Press a key before a slider starts From 6182181ea1d3f1a6809d8631341ef2addbfbd3d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:20:29 +0900 Subject: [PATCH 209/400] Fix simultaneous slider input not allowing both keys --- .../Skinning/Default/SliderBall.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index 82b677e12c..b85610491c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.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; @@ -134,6 +135,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default /// private double? timeToAcceptAnyKeyAfter; + /// + /// The actions that were pressed in the previous frame. + /// + private readonly List lastPressedActions = new List(); + protected override void Update() { base.Update(); @@ -152,8 +158,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { var otherKey = headCircleHitAction == OsuAction.RightButton ? OsuAction.LeftButton : OsuAction.RightButton; - // we can return to accepting all keys if the initial head circle key is the *only* key pressed, or all keys have been released. - if (actions?.Contains(otherKey) != true) + // we can return to accepting all other keys if they were released in the previous frame. + if (!lastPressedActions.Contains(otherKey)) timeToAcceptAnyKeyAfter = Time.Current; } @@ -164,6 +170,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action (actions?.Any(isValidTrackingAction) ?? false); + + lastPressedActions.Clear(); + lastPressedActions.AddRange(actions); } /// From aa7ade8186df4033412bf93db2b970c5ae18a8fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:22:17 +0900 Subject: [PATCH 210/400] Expose presence of `MainCirclePiece` via an interface --- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableSliderRepeat.cs | 2 +- .../Objects/Drawables/DrawableSliderTail.cs | 11 ++++++----- .../Skinning/Default/IHasMainCirclePiece.cs | 12 ++++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index fb6c110b3c..1bf9e76d7d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableHitCircle : DrawableOsuHitObject + public class DrawableHitCircle : DrawableOsuHitObject, IHasMainCirclePiece { public OsuAction? HitAction => HitArea.HitAction; protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 5af2a38559..cc7d9d1b23 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking + public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking, IHasMainCirclePiece { public new SliderRepeat HitObject => (SliderRepeat)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 84b9b881a2..d81af053d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking + public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking, IHasMainCirclePiece { public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject; @@ -35,7 +35,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking { get; set; } - private SkinnableDrawable circlePiece; + public SkinnableDrawable CirclePiece { get; private set; } + private Container scaleContainer; public DrawableSliderTail() @@ -64,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new Drawable[] { // no default for this; only visible in legacy skins. - circlePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) } }, }; @@ -76,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); - circlePiece.FadeInFromZero(HitObject.TimeFadeIn); + CirclePiece.FadeInFromZero(HitObject.TimeFadeIn); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -85,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Debug.Assert(HitObject.HitWindows != null); - (circlePiece.Drawable as IMainCirclePiece)?.Animate(state); + (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); switch (state) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs new file mode 100644 index 0000000000..8bb7629542 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.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.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public interface IHasMainCirclePiece + { + SkinnableDrawable CirclePiece { get; } + } +} From 4da964c3f3235661b8f4b9c9d7c32cb60f3e711b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:22:42 +0900 Subject: [PATCH 211/400] Expose `DrawableSliderRepeat`'s arrow and move transforms to children --- .../Objects/Drawables/DrawableSliderRepeat.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index cc7d9d1b23..5db012c4f8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -28,8 +28,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SkinnableDrawable CirclePiece { get; private set; } + public ReverseArrowPiece Arrow { get; private set; } + private Drawable scaleContainer; - private ReverseArrowPiece arrow; public override bool DisplayResult => false; @@ -57,8 +58,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new Drawable[] { // no default for this; only visible in legacy skins. - CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), - arrow = new ReverseArrowPiece(), + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + Arrow = new ReverseArrowPiece(), } }; @@ -105,8 +110,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; case ArmedState.Hit: - this.FadeOut(animDuration, Easing.Out) - .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + this.FadeOut(animDuration, Easing.Out); + + Arrow.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + CirclePiece.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); break; } } @@ -142,18 +149,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } float aimRotation = MathUtils.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); - while (Math.Abs(aimRotation - arrow.Rotation) > 180) - aimRotation += aimRotation < arrow.Rotation ? 360 : -360; + while (Math.Abs(aimRotation - Arrow.Rotation) > 180) + aimRotation += aimRotation < Arrow.Rotation ? 360 : -360; if (!hasRotation) { - arrow.Rotation = aimRotation; + Arrow.Rotation = aimRotation; hasRotation = true; } else { // If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly). - arrow.Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), arrow.Rotation, aimRotation, 0, 50, Easing.OutQuint); + Arrow.Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Arrow.Rotation, aimRotation, 0, 50, Easing.OutQuint); } } } From 8795c5f0824e1f1cd9b1aae00d429ac2968713f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:23:21 +0900 Subject: [PATCH 212/400] Update osu! editor transform logic to allow adjustments to `DrawableSliderRepeat` and `DrawableSliderTail` --- .../Edit/DrawableOsuEditorRuleset.cs | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index 7227e2b511..aeeae84d14 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -11,6 +11,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -56,43 +57,45 @@ namespace osu.Game.Rulesets.Osu.Edit if (state == ArmedState.Idle || hitAnimations.Value) return; - // adjust the visuals of certain object types to make them stay on screen for longer than usual. - switch (hitObject) + if (hitObject is DrawableHitCircle circle) { - default: - // there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.) - return; + circle.ApproachCircle + .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) + .Expire(); - case DrawableSlider _: - // no specifics to sliders but let them fade slower below. - break; - - case DrawableHitCircle circle: // also handles slider heads - circle.ApproachCircle - .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) - .Expire(); - - circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); - - var circlePieceDrawable = circle.CirclePiece.Drawable; - - // clear any explode animation logic. - circlePieceDrawable.ApplyTransformsAt(circle.HitStateUpdateTime, true); - circlePieceDrawable.ClearTransformsAfter(circle.HitStateUpdateTime, true); - - break; + circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); } - // Get the existing fade out transform - var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); + if (hitObject is IHasMainCirclePiece mainPieceContainer) + { + // clear any explode animation logic. + mainPieceContainer.CirclePiece.ApplyTransformsAt(hitObject.HitStateUpdateTime, true); + mainPieceContainer.CirclePiece.ClearTransformsAfter(hitObject.HitStateUpdateTime, true); + } - if (existing == null) - return; + if (hitObject is DrawableSliderRepeat repeat) + { + repeat.Arrow.ApplyTransformsAt(hitObject.HitStateUpdateTime, true); + repeat.Arrow.ClearTransformsAfter(hitObject.HitStateUpdateTime, true); + } - hitObject.RemoveTransform(existing); + // adjust the visuals of top-level object types to make them stay on screen for longer than usual. + switch (hitObject) + { + case DrawableSlider _: + case DrawableHitCircle _: + // Get the existing fade out transform + var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); - using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime)) - hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + if (existing == null) + return; + + hitObject.RemoveTransform(existing); + + using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime)) + hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + break; + } } } } From d10aac851debb1a7b185a855f76c491833e193ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:30:22 +0900 Subject: [PATCH 213/400] Extract scale constant --- .../Objects/Drawables/DrawableSliderRepeat.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 5db012c4f8..7b4188edab 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -112,8 +112,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out); - Arrow.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); - CirclePiece.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + const float final_scale = 1.5f; + + Arrow.ScaleTo(Scale * final_scale, animDuration, Easing.Out); + CirclePiece.ScaleTo(Scale * final_scale, animDuration, Easing.Out); break; } } From f70e45b1998a97dd8421f0f046c7efcada54d40b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:35:08 +0900 Subject: [PATCH 214/400] Prevent adding null enumerable --- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index b85610491c..c5c95557e6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -172,7 +172,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default (actions?.Any(isValidTrackingAction) ?? false); lastPressedActions.Clear(); - lastPressedActions.AddRange(actions); + if (actions != null) + lastPressedActions.AddRange(actions); } /// From fd5fbaf0dbc5dcf7d5fe475f4ac7c88fe80edbb7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:37:42 +0900 Subject: [PATCH 215/400] Rename ruleset wrapper class --- ...eEditRulesetWrapper.cs => DrawableEditorRulesetWrapper.cs} | 4 ++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Rulesets/Edit/{DrawableEditRulesetWrapper.cs => DrawableEditorRulesetWrapper.cs} (94%) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs similarity index 94% rename from osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs rename to osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index c60d4c7834..62e2539c2a 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Edit /// /// A wrapper for a . Handles adding visual representations of s to the underlying . /// - internal class DrawableEditRulesetWrapper : CompositeDrawable + internal class DrawableEditorRulesetWrapper : CompositeDrawable where TObject : HitObject { public Playfield Playfield => drawableRuleset.Playfield; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Edit [Resolved] private EditorBeatmap beatmap { get; set; } - public DrawableEditRulesetWrapper(DrawableRuleset drawableRuleset) + public DrawableEditorRulesetWrapper(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 736fc47dee..35896d4982 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Edit protected ComposeBlueprintContainer BlueprintContainer { get; private set; } - private DrawableEditRulesetWrapper drawableRulesetWrapper; + private DrawableEditorRulesetWrapper drawableRulesetWrapper; protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Edit try { - drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() })) + drawableRulesetWrapper = new DrawableEditorRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() })) { Clock = EditorClock, ProcessCustomClock = false From 0d0b4ea78a8a07be1ac472641f8123e26f1a4bdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:47:37 +0900 Subject: [PATCH 216/400] Rewrite comment to hopefully be more readable --- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index c5c95557e6..8feeca56e8 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { var otherKey = headCircleHitAction == OsuAction.RightButton ? OsuAction.LeftButton : OsuAction.RightButton; - // we can return to accepting all other keys if they were released in the previous frame. + // we can start accepting any key once all other keys have been released in the previous frame. if (!lastPressedActions.Contains(otherKey)) timeToAcceptAnyKeyAfter = Time.Current; } From 58ebec48037499b162fb5045c767812a22f82fb5 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Mon, 26 Apr 2021 19:00:40 +0800 Subject: [PATCH 217/400] Move BindValueChanged hooks to LoadComplete() --- .../Objects/Drawables/DrawableNote.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 2d2fba0ad5..49536080ab 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private OsuColour colours { get; set; } + [Resolved] + private BeatDivisorFinder beatDivisorFinder { get; set; } + private readonly Bindable configColourCodedNotes = new Bindable(); protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; @@ -48,17 +51,20 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } [BackgroundDependencyLoader(true)] - private void load(ManiaRulesetConfigManager rulesetConfig, BeatDivisorFinder beatDivisorFinder) + private void load(ManiaRulesetConfigManager rulesetConfig) + { + rulesetConfig?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + } + + protected override void LoadComplete() { if (beatDivisorFinder != null) { - rulesetConfig?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); - HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = beatDivisorFinder.FindDivisor(HitObject), true); - - snap.BindValueChanged(_ => updateSnapColour(), true); - configColourCodedNotes.BindValueChanged(_ => updateSnapColour()); } + + snap.BindValueChanged(_ => updateSnapColour()); + configColourCodedNotes.BindValueChanged(_ => updateSnapColour(), true); } protected override void OnDirectionChanged(ValueChangedEvent e) From 559d403abec8046cd8331fbc45523676ae2452f0 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Mon, 26 Apr 2021 19:05:12 +0800 Subject: [PATCH 218/400] Rename ColourCodedNotes to TimingBasedNoteColouring --- .../Configuration/ManiaRulesetConfigManager.cs | 4 ++-- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 4 ++-- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 87a4689689..ac8168dfc9 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Configuration SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); - SetDefault(ManiaRulesetSetting.ColourCodedNotes, false); + SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings @@ -36,6 +36,6 @@ namespace osu.Game.Rulesets.Mania.Configuration { ScrollTime, ScrollDirection, - ColourCodedNotes + TimingBasedNoteColouring } } diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 552c793096..1c89d9cd00 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -39,8 +39,8 @@ namespace osu.Game.Rulesets.Mania }, new SettingsCheckbox { - LabelText = "Colour-coded notes", - Current = config.GetBindable(ManiaRulesetSetting.ColourCodedNotes), + LabelText = "Timing-based note colouring", + Current = config.GetBindable(ManiaRulesetSetting.TimingBasedNoteColouring), } }; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 49536080ab..102cd485dc 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private BeatDivisorFinder beatDivisorFinder { get; set; } - private readonly Bindable configColourCodedNotes = new Bindable(); + private readonly Bindable configTimingBasedNoteColouring = new Bindable(); protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [BackgroundDependencyLoader(true)] private void load(ManiaRulesetConfigManager rulesetConfig) { - rulesetConfig?.BindWith(ManiaRulesetSetting.ColourCodedNotes, configColourCodedNotes); + rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring); } protected override void LoadComplete() @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } snap.BindValueChanged(_ => updateSnapColour()); - configColourCodedNotes.BindValueChanged(_ => updateSnapColour(), true); + configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour(), true); } protected override void OnDirectionChanged(ValueChangedEvent e) @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private void updateSnapColour() { - Colour = configColourCodedNotes.Value + Colour = configTimingBasedNoteColouring.Value ? BindableBeatDivisor.GetColourFor(snap.Value, colours) : Color4.White; } From 6560dc2d1f579947218d1fa88853775efb0d1792 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 20:46:44 +0900 Subject: [PATCH 219/400] Fix exported replays being wrapped in zip packages --- osu.Game/Database/ArchiveModelManager.cs | 20 +++++++++++++++----- osu.Game/Scoring/ScoreManager.cs | 10 ++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index d809dbcb01..6719351530 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -422,15 +422,25 @@ namespace osu.Game.Database if (retrievedItem == null) throw new ArgumentException("Specified model could not be found", nameof(item)); + using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) + ExportModelTo(retrievedItem, outputStream); + + exportStorage.OpenInNativeExplorer(); + } + + /// + /// Exports an item to the given output stream. + /// + /// The item to export. + /// The output stream to export to. + protected virtual void ExportModelTo(TModel model, Stream outputStream) + { using (var archive = ZipArchive.Create()) { - foreach (var file in retrievedItem.Files) + foreach (var file in model.Files) archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath)); - using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) - archive.SaveTo(outputStream); - - exportStorage.OpenInNativeExplorer(); + archive.SaveTo(outputStream); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index c7ee26c248..9d3b952ada 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -72,6 +72,16 @@ namespace osu.Game.Scoring } } + protected override void ExportModelTo(ScoreInfo model, Stream outputStream) + { + var file = model.Files.SingleOrDefault(); + if (file == null) + return; + + using (var inputStream = Files.Storage.GetStream(file.FileInfo.StoragePath)) + inputStream.CopyTo(outputStream); + } + protected override IEnumerable GetStableImportPaths(Storage storage) => storage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)) .Select(path => storage.GetFullPath(path)); From 213ac88a8b34b7d1caa57183945b501b3c828e1a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 20:52:20 +0900 Subject: [PATCH 220/400] Fix exported scores not being compatible with osu-stable --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index db7e51e833..56c4e75864 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -95,7 +95,7 @@ namespace osu.Game.Scoring.Legacy foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { - replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); + replayData.Append(FormattableString.Invariant($"{(int)Math.Round(f.Time - lastF.Time)}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); lastF = f; } } From 7b9ed924be4ab084101d24fa551ddaee8297f410 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:07:30 +0200 Subject: [PATCH 221/400] Rename snapping methods Further separates them from `IBeatSnapProvider`'s `SnapTime`, and groups them together more, to prevent confusion between the two interfaces. Also changes the xmldoc of the reference time to that of `IBeatSnapProvider` for consistency. --- osu.Game/Beatmaps/Beatmap.cs | 10 +++++----- osu.Game/Beatmaps/IBeatmap.cs | 12 ++++++------ osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs | 6 +++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 10 +++++----- osu.Game/Screens/Play/GameplayBeatmap.cs | 8 ++++---- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 1ce01aee24..66b8f169ef 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -75,7 +75,7 @@ namespace osu.Game.Beatmaps return mostCommon.beatLength; } - public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) + public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) { var timingPoint = ControlPointInfo.TimingPointAt(referenceTime ?? time); var beatLength = timingPoint.BeatLength / beatDivisor; @@ -84,14 +84,14 @@ namespace osu.Game.Beatmaps return (int)(timingPoint.Time + beatLengths * beatLength); } - public int SnapTimeAnyDivisor(double time, double? referenceTime = null) + public int ClosestSnapTime(double time, double? referenceTime = null) { - return SnapTimeForDivisor(time, ClosestBeatSnapDivisor(time, referenceTime), referenceTime); + return ClosestSnapTime(time, ClosestBeatDivisor(time, referenceTime), referenceTime); } - public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) + public int ClosestBeatDivisor(double time, double? referenceTime = null) { - double getUnsnap(int divisor) => Math.Abs(time - SnapTimeForDivisor(time, divisor, referenceTime)); + double getUnsnap(int divisor) => Math.Abs(time - ClosestSnapTime(time, divisor, referenceTime)); int[] divisors = BindableBeatDivisor.VALID_DIVISORS; double smallestUnsnap = divisors.Min(getUnsnap); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3b043cb59b..679d639fd1 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -56,22 +56,22 @@ namespace osu.Game.Beatmaps /// /// The time to find the closest snapped time to. /// The beat divisor to snap to. - /// The time at which the timing point is retrieved, by default same as time. - int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null); + /// An optional reference point to use for timing point lookup. + int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null); /// /// Returns the time on any valid beat divisor closest to the given time. /// /// The time to find the closest snapped time to. - /// The time at which the timing point is retrieved, by default same as time. - int SnapTimeAnyDivisor(double time, double? referenceTime = null); + /// An optional reference point to use for timing point lookup. + int ClosestSnapTime(double time, double? referenceTime = null); /// /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned. /// /// The time to find the closest beat snap divisor to. - /// The time at which the timing point is retrieved, by default same as time. - int ClosestBeatSnapDivisor(double time, double? referenceTime = null); + /// An optional reference point to use for timing point lookup. + int ClosestBeatDivisor(double time, double? referenceTime = null); /// /// Creates a shallow-clone of this beatmap and returns it. diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs index d15dc6f179..ca268652a9 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Edit.Checks { foreach (var hitobject in playableBeatmap.HitObjects) { - double startUnsnap = hitobject.StartTime - playableBeatmap.SnapTimeAnyDivisor(hitobject.StartTime); + double startUnsnap = hitobject.StartTime - playableBeatmap.ClosestSnapTime(hitobject.StartTime); string startPostfix = hitobject is IHasDuration ? "start" : ""; foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix)) yield return issue; @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1); double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1); - double repeatUnsnap = repeatTime - playableBeatmap.SnapTimeAnyDivisor(repeatTime); + double repeatUnsnap = repeatTime - playableBeatmap.ClosestSnapTime(repeatTime); foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat")) yield return issue; } @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitobject is IHasDuration hasDuration) { - double endUnsnap = hasDuration.EndTime - playableBeatmap.SnapTimeAnyDivisor(hasDuration.EndTime); + double endUnsnap = hasDuration.EndTime - playableBeatmap.ClosestSnapTime(hasDuration.EndTime); foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end")) yield return issue; } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 9334814e2a..72fb0ac9e9 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -301,16 +301,16 @@ namespace osu.Game.Screens.Edit return list.Count - 1; } - public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) + public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) { - return PlayableBeatmap.SnapTimeForDivisor(time, beatDivisor, referenceTime); + return PlayableBeatmap.ClosestSnapTime(time, beatDivisor, referenceTime); } - public int SnapTimeAnyDivisor(double time, double? referenceTime = null) => PlayableBeatmap.SnapTimeAnyDivisor(time, referenceTime); + public int ClosestSnapTime(double time, double? referenceTime = null) => PlayableBeatmap.ClosestSnapTime(time, referenceTime); - public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatSnapDivisor(time, referenceTime); + public int ClosestBeatDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatDivisor(time, referenceTime); - public double SnapTime(double time, double? referenceTime) => SnapTimeForDivisor(time, BeatDivisor, referenceTime); + public double SnapTime(double time, double? referenceTime) => ClosestSnapTime(time, BeatDivisor, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs index a3a2bbd41b..92f58c8759 100644 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ b/osu.Game/Screens/Play/GameplayBeatmap.cs @@ -45,14 +45,14 @@ namespace osu.Game.Screens.Play public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); - public int SnapTimeForDivisor(double time, int beatDivisor, double? referenceTime = null) + public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) { - return PlayableBeatmap.SnapTimeForDivisor(time, beatDivisor, referenceTime); + return PlayableBeatmap.ClosestSnapTime(time, beatDivisor, referenceTime); } - public int SnapTimeAnyDivisor(double time, double? referenceTime = null) => PlayableBeatmap.SnapTimeAnyDivisor(time, referenceTime); + public int ClosestSnapTime(double time, double? referenceTime = null) => PlayableBeatmap.ClosestSnapTime(time, referenceTime); - public int ClosestBeatSnapDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatSnapDivisor(time, referenceTime); + public int ClosestBeatDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatDivisor(time, referenceTime); public IBeatmap Clone() => PlayableBeatmap.Clone(); From 9b9c473616c6d29a550881e17b9f94cc6271ade8 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:17:38 +0200 Subject: [PATCH 222/400] Remove redundant string formatting --- osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs index ca268652a9..ff270b6d60 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Edit.Checks public abstract class IssueTemplateUnsnap : IssueTemplate { protected IssueTemplateUnsnap(ICheck check, IssueType type) - : base(check, type, "{0:0.##} is unsnapped by {1:0.##} ms.") + : base(check, type, "{0} is unsnapped by {1:0.##} ms.") { } From 71f880aa94eaf3f9defd3b943c1f949293c32631 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 17:44:46 +0200 Subject: [PATCH 223/400] Fix duplicate code in unsnap test --- osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs index 88939c43ce..bac3c41cb0 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs @@ -70,14 +70,9 @@ namespace osu.Game.Tests.Editing.Checks public void TestSliderSnapped() { // Slider ends are naturally < 1 ms unsnapped because of how SV works. - var mockSlider = new Mock(); - mockSlider.SetupGet(s => s.StartTime).Returns(100); - mockSlider.As().Setup(r => r.RepeatCount).Returns(0); - mockSlider.As().Setup(d => d.Duration).Returns(400.75d); - assertOk(new List { - mockSlider.Object + getSliderMock(startTime: 100, endTime: 400.75d).Object }); } From 08a232f7fad662384426f21b3131e36828b9de62 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 26 Apr 2021 20:08:40 +0200 Subject: [PATCH 224/400] Add method to safely refresh DrawableHitObject transforms --- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7739994527..679c86072b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -14,13 +14,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; +using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; -using osu.Game.Configuration; -using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.UI; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -429,6 +429,13 @@ namespace osu.Game.Rulesets.Objects.Drawables base.ClearTransformsAfter(double.MinValue, true); } + /// + /// Removes all previously applied transforms, then reapplies a new set of transforms with potentially different parameters. + /// The transforms will use the current , and they will use the appropriate start times. + /// This also takes in account potential overrides defined in . + /// + protected void RefreshStateTransforms() => updateState(State.Value, true); + /// /// Apply (generally fade-in) transforms leading into the start time. /// The local drawable hierarchy is recursively delayed to for convenience. From a3570e18dd3c860b0a3942d5c0ba3cf8593471c8 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:17:18 +0200 Subject: [PATCH 225/400] Add concurrent objects check Here we use `IHasColumn` to support rulesets with columns, and so I moved that interface out into `osu.Game` from `osu.Game.Rulesets.Mania`. We also use the same threshold as the unsnap check to ensure that no problems slip through. Specifically where an object is simultaneously not concurrent and not unsnapped but still on the same tick. --- .../Objects/ManiaHitObject.cs | 1 - .../Edit/Checks/CheckConcurrentObjects.cs | 88 +++++++++++++++++++ osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs | 4 +- .../Rulesets}/Objects/Types/IHasColumn.cs | 2 +- 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs rename {osu.Game.Rulesets.Mania => osu.Game/Rulesets}/Objects/Types/IHasColumn.cs (90%) diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index 27bf50493d..6289744df1 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Game.Rulesets.Mania.Objects.Types; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs new file mode 100644 index 0000000000..7c41569fab --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -0,0 +1,88 @@ +// 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.Checks.Components; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckConcurrentObjects : ICheck + { + // We guarantee that the objects are either treated as concurrent or unsnapped when near the same beat divisor. + private const double ms_leniency = CheckUnsnaps.UNSNAP_MS_THRESHOLD; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Concurrent hitobjects"); + + public virtual IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateConcurrentSame(this), + new IssueTemplateConcurrentDifferent(this) + }; + + public virtual IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + { + for (int i = 0; i < playableBeatmap.HitObjects.Count - 1; ++i) + { + var hitobject = playableBeatmap.HitObjects[i]; + + for (int j = i + 1; j < playableBeatmap.HitObjects.Count; ++j) + { + var nextHitobject = playableBeatmap.HitObjects[j]; + + // Accounts for rulesets with hitobjects separated by columns, such as Mania. + // In these cases we only care about concurrent objects within the same column. + if ((hitobject as IHasColumn)?.Column != (nextHitobject as IHasColumn)?.Column) + continue; + + // Two hitobjects cannot be concurrent without also being concurrent with all objects in between. + // So if the next object is not concurrent, then we know no future objects will be either. + if (!areConcurrent(hitobject, nextHitobject)) + break; + + if (hitobject.GetType() == nextHitobject.GetType()) + yield return new IssueTemplateConcurrentSame(this).Create(hitobject, nextHitobject); + else + yield return new IssueTemplateConcurrentDifferent(this).Create(hitobject, nextHitobject); + } + } + } + + protected bool areConcurrent(HitObject hitobject, HitObject nextHitobject) => nextHitobject.StartTime <= hitobject.GetEndTime() + ms_leniency; + + public abstract class IssueTemplateConcurrent : IssueTemplate + { + protected IssueTemplateConcurrent(ICheck check, string unformattedMessage) + : base(check, IssueType.Problem, unformattedMessage) + { + } + + public Issue Create(HitObject hitobject, HitObject nextHitobject) + { + var hitobjects = new List { hitobject, nextHitobject }; + return new Issue(hitobjects, this, hitobject.GetType().Name, nextHitobject.GetType().Name) + { + Time = nextHitobject.StartTime + }; + } + } + + public class IssueTemplateConcurrentSame : IssueTemplateConcurrent + { + public IssueTemplateConcurrentSame(ICheck check) + : base(check, "{0}s are concurrent here.") + { + } + } + + public class IssueTemplateConcurrentDifferent : IssueTemplateConcurrent + { + public IssueTemplateConcurrentDifferent(ICheck check) + : base(check, "{0} and {1} are concurrent here.") + { + } + } + } +} diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs index ff270b6d60..564ef13d8f 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Edit.Checks { public class CheckUnsnaps : ICheck { - private const double unsnap_ms_threshold = 2; + public const double UNSNAP_MS_THRESHOLD = 2; public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Unsnapped hitobjects"); @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Edit.Checks private IEnumerable getUnsnapIssues(HitObject hitobject, double unsnap, double time, string postfix = "") { - if (Math.Abs(unsnap) >= unsnap_ms_threshold) + if (Math.Abs(unsnap) >= UNSNAP_MS_THRESHOLD) yield return new IssueTemplate2MsOrMore(this).Create(hitobject, unsnap, time, postfix); else if (Math.Abs(unsnap) >= 1) yield return new IssueTemplate1MsOrMore(this).Create(hitobject, unsnap, time, postfix); diff --git a/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs b/osu.Game/Rulesets/Objects/Types/IHasColumn.cs similarity index 90% rename from osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs rename to osu.Game/Rulesets/Objects/Types/IHasColumn.cs index 1ea3138828..dc07cfbb6a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasColumn.cs @@ -1,7 +1,7 @@ // 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.Mania.Objects.Types +namespace osu.Game.Rulesets.Objects.Types { /// /// A type of hit object which lies in one of a number of predetermined columns. From b8cdcf56c03ef5f4dc27e204c7efece3ffd4572f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:22:24 +0200 Subject: [PATCH 226/400] Add concurrent object check tests --- .../Checks/CheckConcurrentObjectsTest.cs | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs new file mode 100644 index 0000000000..0f771427ee --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs @@ -0,0 +1,194 @@ +// 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 Moq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckConcurrentObjectsTest + { + private CheckConcurrentObjects check; + + [SetUp] + public void Setup() + { + check = new CheckConcurrentObjects(); + } + + [Test] + public void TestCirclesSeparate() + { + assertOk(new List + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 150 } + }); + } + + [Test] + public void TestCirclesConcurrent() + { + assertConcurrentSame(new List + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 100 } + }); + } + + [Test] + public void TestCirclesAlmostConcurrent() + { + assertConcurrentSame(new List + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 101 } + }); + } + + [Test] + public void TestSlidersSeparate() + { + assertOk(new List + { + getSliderMock(startTime: 100, endTime: 400.75d).Object, + getSliderMock(startTime: 500, endTime: 900.75d).Object + }); + } + + [Test] + public void TestSlidersConcurrent() + { + assertConcurrentSame(new List + { + getSliderMock(startTime: 100, endTime: 400.75d).Object, + getSliderMock(startTime: 300, endTime: 700.75d).Object + }); + } + + [Test] + public void TestSlidersAlmostConcurrent() + { + assertConcurrentSame(new List + { + getSliderMock(startTime: 100, endTime: 400.75d).Object, + getSliderMock(startTime: 402, endTime: 902.75d).Object + }); + } + + [Test] + public void TestSliderAndCircleConcurrent() + { + assertConcurrentDifferent(new List + { + getSliderMock(startTime: 100, endTime: 400.75d).Object, + new HitCircle { StartTime = 300 } + }); + } + + [Test] + public void TestManyObjectsConcurrent() + { + var hitobjects = new List + { + getSliderMock(startTime: 100, endTime: 400.75d).Object, + new HitCircle { StartTime = 300 }, + getSliderMock(startTime: 200, endTime: 500.75d).Object + }; + + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(3)); + Assert.That(issues.Where(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentDifferent).ToList(), Has.Count.EqualTo(2)); + Assert.That(issues.Any(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentSame)); + } + + [Test] + public void TestHoldNotesSeparateOnSameColumn() + { + assertOk(new List + { + getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object, + getHoldNoteMock(startTime: 500, endTime: 900.75d, column: 1).Object + }); + } + + [Test] + public void TestHoldNotesConcurrentOnDifferentColumns() + { + assertOk(new List + { + getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object, + getHoldNoteMock(startTime: 300, endTime: 700.75d, column: 2).Object + }); + } + + [Test] + public void TestHoldNotesConcurrentOnSameColumn() + { + assertConcurrentSame(new List + { + getHoldNoteMock(startTime: 100, endTime: 400.75d, column: 1).Object, + getHoldNoteMock(startTime: 300, endTime: 700.75d, column: 1).Object + }); + } + + private Mock getSliderMock(double startTime, double endTime, int repeats = 0) + { + var mock = new Mock(); + mock.SetupGet(s => s.StartTime).Returns(startTime); + mock.As().Setup(r => r.RepeatCount).Returns(repeats); + mock.As().Setup(d => d.EndTime).Returns(endTime); + + return mock; + } + + private Mock getHoldNoteMock(double startTime, double endTime, int column) + { + var mock = new Mock(); + mock.SetupGet(s => s.StartTime).Returns(startTime); + mock.As().Setup(d => d.EndTime).Returns(endTime); + mock.As().Setup(c => c.Column).Returns(column); + + return mock; + } + + private void assertOk(List hitobjects) + { + Assert.That(check.Run(getPlayableBeatmap(hitobjects), null), Is.Empty); + } + + private void assertConcurrentSame(List hitobjects, int count = 1) + { + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentSame)); + } + + private void assertConcurrentDifferent(List hitobjects, int count = 1) + { + var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + + Assert.That(issues, Has.Count.EqualTo(count)); + Assert.That(issues.All(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentDifferent)); + } + + private IBeatmap getPlayableBeatmap(List hitobjects) + { + return new Beatmap + { + HitObjects = hitobjects + }; + } + } +} From b9e4f73f78fa5bf3cdacefdd10d3a186079913be Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:28:59 +0200 Subject: [PATCH 227/400] Add concurrent objects check to `BeatmapVerifier` --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index aa3459a01a..6754d62a11 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -25,7 +25,8 @@ namespace osu.Game.Rulesets.Edit new CheckAudioQuality(), // Compose - new CheckUnsnaps() + new CheckUnsnaps(), + new CheckConcurrentObjects() }; public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) From ce258febf6de3ec3e3e1b53cdd240107ca46028c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:32:44 +0200 Subject: [PATCH 228/400] Rename `CheckUnsnaps` -> `CheckUnsnappedObjects` Will potentially have `CheckUnsnappedKiai` or similar later, so this is worth specifying. Also consistent with `CheckConcurrentObjects`, which will likely have a `CheckConcurrentLines` later. --- ...UnsnapsTest.cs => CheckUnsnappedObjectsTest.cs} | 14 +++++++------- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 2 +- .../Rulesets/Edit/Checks/CheckConcurrentObjects.cs | 2 +- .../{CheckUnsnaps.cs => CheckUnsnappedObjects.cs} | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) rename osu.Game.Tests/Editing/Checks/{CheckUnsnapsTest.cs => CheckUnsnappedObjectsTest.cs} (93%) rename osu.Game/Rulesets/Edit/Checks/{CheckUnsnaps.cs => CheckUnsnappedObjects.cs} (98%) diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs similarity index 93% rename from osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs rename to osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs index bac3c41cb0..f8cac331bc 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnsnapsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs @@ -15,15 +15,15 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Tests.Editing.Checks { [TestFixture] - public class CheckUnsnapsTest + public class CheckUnsnappedObjectsTest { - private CheckUnsnaps check; + private CheckUnsnappedObjects check; private ControlPointInfo cpi; [SetUp] public void Setup() { - check = new CheckUnsnaps(); + check = new CheckUnsnappedObjects(); cpi = new ControlPointInfo(); cpi.Add(100, new TimingControlPoint { BeatLength = 100 }); @@ -108,8 +108,8 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(2)); - Assert.That(issues.Any(issue => issue.Template is CheckUnsnaps.IssueTemplate1MsOrMore)); - Assert.That(issues.Any(issue => issue.Template is CheckUnsnaps.IssueTemplate2MsOrMore)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate1MsOrMore)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate2MsOrMore)); } private Mock getSliderMock(double startTime, double endTime, int repeats = 0) @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); - Assert.That(issues.All(issue => issue.Template is CheckUnsnaps.IssueTemplate1MsOrMore)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate1MsOrMore)); } private void assert2Ms(List hitobjects, int count = 1) @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); - Assert.That(issues.All(issue => issue.Template is CheckUnsnaps.IssueTemplate2MsOrMore)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate2MsOrMore)); } private IBeatmap getPlayableBeatmap(List hitobjects) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 6754d62a11..2f7b7b0ab8 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit new CheckAudioQuality(), // Compose - new CheckUnsnaps(), + new CheckUnsnappedObjects(), new CheckConcurrentObjects() }; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs index 7c41569fab..bcc8fead18 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Edit.Checks public class CheckConcurrentObjects : ICheck { // We guarantee that the objects are either treated as concurrent or unsnapped when near the same beat divisor. - private const double ms_leniency = CheckUnsnaps.UNSNAP_MS_THRESHOLD; + private const double ms_leniency = CheckUnsnappedObjects.UNSNAP_MS_THRESHOLD; public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Concurrent hitobjects"); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs similarity index 98% rename from osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs rename to osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index 564ef13d8f..cdcf8a6b80 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnaps.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Edit.Checks { - public class CheckUnsnaps : ICheck + public class CheckUnsnappedObjects : ICheck { public const double UNSNAP_MS_THRESHOLD = 2; From 9ad30da72943d4bae44dc82e7438d8bb84e14ce0 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 16:41:26 -0400 Subject: [PATCH 229/400] Show a notification if game is run as administrator --- osu.Desktop/OsuGameDesktop.cs | 9 ++++++ osu.Desktop/osu.Desktop.csproj | 1 + osu.Game/Admin/AdminChecker.cs | 57 ++++++++++++++++++++++++++++++++++ osu.Game/OsuGame.cs | 5 +++ 4 files changed, 72 insertions(+) create mode 100644 osu.Game/Admin/AdminChecker.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 0c21c75290..1ce79b3b49 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Versioning; +using System.Security.Principal; using System.Threading.Tasks; using Microsoft.Win32; using osu.Desktop.Overlays; @@ -20,6 +21,7 @@ using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; +using osu.Game.Admin; using osu.Game.IO; namespace osu.Desktop @@ -102,6 +104,8 @@ namespace osu.Desktop } } + protected override AdminChecker CreateAdminChecker() => new DesktopAdminChecker(); + protected override void LoadComplete() { base.LoadComplete(); @@ -180,5 +184,10 @@ namespace osu.Desktop Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); } } + + private class DesktopAdminChecker : AdminChecker + { + protected override bool IsAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; + } } } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 3e0f0cb7f6..6bd0f64218 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -25,6 +25,7 @@ + diff --git a/osu.Game/Admin/AdminChecker.cs b/osu.Game/Admin/AdminChecker.cs new file mode 100644 index 0000000000..b81e34653e --- /dev/null +++ b/osu.Game/Admin/AdminChecker.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 osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Admin +{ + /// + /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. + /// + public class AdminChecker : CompositeDrawable + { + [Resolved] + protected NotificationOverlay Notifications { get; private set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + if (IsAdmin()) + Notifications.Post(new AdminNotification()); + } + + protected virtual bool IsAdmin() => false; + + private class AdminNotification : SimpleNotification + { + public override bool IsImportant => true; + + public AdminNotification() + { + bool isUnix = RuntimeInfo.IsUnix; + Text = $"Running osu! as {(isUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay) + { + Icon = FontAwesome.Solid.ShieldAlt; + IconBackgound.Colour = colours.YellowDark; + + Activated = delegate + { + notificationOverlay.Hide(); + return true; + }; + } + } + } + + +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 28f32ba455..358e85b247 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -28,6 +28,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Threading; +using osu.Game.Admin; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics; @@ -450,6 +451,8 @@ namespace osu.Game protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); + protected virtual AdminChecker CreateAdminChecker() => new AdminChecker(); + protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -673,6 +676,8 @@ namespace osu.Game // dependency on notification overlay, dependent by settings overlay loadComponentSingleFile(CreateUpdateManager(), Add, true); + loadComponentSingleFile(CreateAdminChecker(), Add, false); + // overlay elements loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); From 0f0870c8b875451c2f7ce3e724fb63ee05b776ec Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 00:36:26 +0200 Subject: [PATCH 230/400] Sort objects by time in concurrent check test --- osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs index 0f771427ee..ffe5d34e67 100644 --- a/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs @@ -101,8 +101,8 @@ namespace osu.Game.Tests.Editing.Checks var hitobjects = new List { getSliderMock(startTime: 100, endTime: 400.75d).Object, - new HitCircle { StartTime = 300 }, - getSliderMock(startTime: 200, endTime: 500.75d).Object + getSliderMock(startTime: 200, endTime: 500.75d).Object, + new HitCircle { StartTime = 300 } }; var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); From 6d5883abcb6293c54a362c7e765e07a8441b70fd Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 01:19:38 +0200 Subject: [PATCH 231/400] Return result of local variable instead --- osu.Game/Beatmaps/Beatmap.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 66b8f169ef..e3a11e2326 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -95,9 +95,8 @@ namespace osu.Game.Beatmaps int[] divisors = BindableBeatDivisor.VALID_DIVISORS; double smallestUnsnap = divisors.Min(getUnsnap); - int closestDivisor = divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); - return closestDivisor; + return divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); } IBeatmap IBeatmap.Clone() => Clone(); From 217ff8238ea50abeea0ccb62319cd785a91d39f3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 01:23:03 +0200 Subject: [PATCH 232/400] Add snapping time comment --- osu.Game/Beatmaps/Beatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index e3a11e2326..6515540527 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -81,6 +81,7 @@ namespace osu.Game.Beatmaps var beatLength = timingPoint.BeatLength / beatDivisor; var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + // Casting to int matches stable. return (int)(timingPoint.Time + beatLengths * beatLength); } From a3c1b1fd52d1a74df5df536d05df6550852c9ab5 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 01:24:38 +0200 Subject: [PATCH 233/400] Fix accessibility of `areConcurrent` --- osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs index bcc8fead18..6e8355024e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Edit.Checks } } - protected bool areConcurrent(HitObject hitobject, HitObject nextHitobject) => nextHitobject.StartTime <= hitobject.GetEndTime() + ms_leniency; + private bool areConcurrent(HitObject hitobject, HitObject nextHitobject) => nextHitobject.StartTime <= hitobject.GetEndTime() + ms_leniency; public abstract class IssueTemplateConcurrent : IssueTemplate { From 260dd06f4707f378ae2fd7a6fda4852d6160105a Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 17:41:04 -0400 Subject: [PATCH 234/400] Move AdminChecker to osu.Desktop.Admin --- {osu.Game => osu.Desktop}/Admin/AdminChecker.cs | 13 ++++++------- osu.Desktop/OsuGameDesktop.cs | 12 +++--------- osu.Game/OsuGame.cs | 5 ----- 3 files changed, 9 insertions(+), 21 deletions(-) rename {osu.Game => osu.Desktop}/Admin/AdminChecker.cs (75%) diff --git a/osu.Game/Admin/AdminChecker.cs b/osu.Desktop/Admin/AdminChecker.cs similarity index 75% rename from osu.Game/Admin/AdminChecker.cs rename to osu.Desktop/Admin/AdminChecker.cs index b81e34653e..b9f0d68694 100644 --- a/osu.Game/Admin/AdminChecker.cs +++ b/osu.Desktop/Admin/AdminChecker.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.Security.Principal; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; @@ -9,7 +11,7 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Game.Admin +namespace osu.Desktop.Admin { /// /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. @@ -22,11 +24,11 @@ namespace osu.Game.Admin protected override void LoadComplete() { base.LoadComplete(); - if (IsAdmin()) + if (isAdmin()) Notifications.Post(new AdminNotification()); } - protected virtual bool IsAdmin() => false; + private bool isAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; private class AdminNotification : SimpleNotification { @@ -34,8 +36,7 @@ namespace osu.Game.Admin public AdminNotification() { - bool isUnix = RuntimeInfo.IsUnix; - Text = $"Running osu! as {(isUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; + Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; } [BackgroundDependencyLoader] @@ -52,6 +53,4 @@ namespace osu.Game.Admin } } } - - } diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 1ce79b3b49..77f968e1b6 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -7,9 +7,9 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Versioning; -using System.Security.Principal; using System.Threading.Tasks; using Microsoft.Win32; +using osu.Desktop.Admin; using osu.Desktop.Overlays; using osu.Framework.Platform; using osu.Game; @@ -21,7 +21,6 @@ using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; -using osu.Game.Admin; using osu.Game.IO; namespace osu.Desktop @@ -104,8 +103,6 @@ namespace osu.Desktop } } - protected override AdminChecker CreateAdminChecker() => new DesktopAdminChecker(); - protected override void LoadComplete() { base.LoadComplete(); @@ -117,6 +114,8 @@ namespace osu.Desktop if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) LoadComponentAsync(new GameplayWinKeyBlocker(), Add); + + LoadComponentAsync(new AdminChecker(), Add); } protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) @@ -184,10 +183,5 @@ namespace osu.Desktop Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); } } - - private class DesktopAdminChecker : AdminChecker - { - protected override bool IsAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; - } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 358e85b247..28f32ba455 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -28,7 +28,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Threading; -using osu.Game.Admin; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics; @@ -451,8 +450,6 @@ namespace osu.Game protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); - protected virtual AdminChecker CreateAdminChecker() => new AdminChecker(); - protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -676,8 +673,6 @@ namespace osu.Game // dependency on notification overlay, dependent by settings overlay loadComponentSingleFile(CreateUpdateManager(), Add, true); - loadComponentSingleFile(CreateAdminChecker(), Add, false); - // overlay elements loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); From 9e49ecb57311b3d8e83c9c8655cca8e13e0342bb Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 02:23:06 +0200 Subject: [PATCH 235/400] Remove unused `virtual` keywords Added these in a previous iteration, where I had the mania variant inherit this class. No longer necessary as `IHasColumn` was used to make this check more generic. --- osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs index 6e8355024e..ddebe2923a 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -16,13 +16,13 @@ namespace osu.Game.Rulesets.Edit.Checks public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Concurrent hitobjects"); - public virtual IEnumerable PossibleTemplates => new IssueTemplate[] + public IEnumerable PossibleTemplates => new IssueTemplate[] { new IssueTemplateConcurrentSame(this), new IssueTemplateConcurrentDifferent(this) }; - public virtual IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { for (int i = 0; i < playableBeatmap.HitObjects.Count - 1; ++i) { From 7a6e9e5070b63d7b1563108f170ca676994e8b5f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 02:32:57 +0200 Subject: [PATCH 236/400] Change category of unsnap check to timing Makes more sense, as this is typically the result of timing changes. --- osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index cdcf8a6b80..74a2ce2fd7 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Edit.Checks { public const double UNSNAP_MS_THRESHOLD = 2; - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Unsnapped hitobjects"); + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Timing, "Unsnapped hitobjects"); public IEnumerable PossibleTemplates => new IssueTemplate[] { From c3bad1d4c599e43846c8f0e596b370bc082ef5ed Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 21:05:18 -0400 Subject: [PATCH 237/400] Rename AdminChecker to ElevatedPrivilegesChecker, refactor elevated check --- ...hecker.cs => ElevatedPrivilegesChecker.cs} | 30 ++++++++++++++----- osu.Desktop/OsuGameDesktop.cs | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) rename osu.Desktop/Admin/{AdminChecker.cs => ElevatedPrivilegesChecker.cs} (64%) diff --git a/osu.Desktop/Admin/AdminChecker.cs b/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs similarity index 64% rename from osu.Desktop/Admin/AdminChecker.cs rename to osu.Desktop/Admin/ElevatedPrivilegesChecker.cs index b9f0d68694..ea4aab0f85 100644 --- a/osu.Desktop/Admin/AdminChecker.cs +++ b/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs @@ -5,7 +5,7 @@ using System; using System.Security.Principal; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Overlays; @@ -16,7 +16,7 @@ namespace osu.Desktop.Admin /// /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. /// - public class AdminChecker : CompositeDrawable + public class ElevatedPrivilegesChecker : Component { [Resolved] protected NotificationOverlay Notifications { get; private set; } @@ -24,17 +24,31 @@ namespace osu.Desktop.Admin protected override void LoadComplete() { base.LoadComplete(); - if (isAdmin()) - Notifications.Post(new AdminNotification()); + + bool elevated = false; + + if (OperatingSystem.IsWindows()) + { + var windowsIdentity = WindowsIdentity.GetCurrent(); + var windowsPrincipal = new WindowsPrincipal(windowsIdentity); + elevated = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + } + else if (RuntimeInfo.IsUnix) + { + elevated = Mono.Unix.Native.Syscall.geteuid() == 0; + } + + if (!elevated) + return; + + Notifications.Post(new ElevatedPrivilegesNotification()); } - private bool isAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; - - private class AdminNotification : SimpleNotification + private class ElevatedPrivilegesNotification : SimpleNotification { public override bool IsImportant => true; - public AdminNotification() + public ElevatedPrivilegesNotification() { Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; } diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 77f968e1b6..9f7854779e 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -115,7 +115,7 @@ namespace osu.Desktop if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) LoadComponentAsync(new GameplayWinKeyBlocker(), Add); - LoadComponentAsync(new AdminChecker(), Add); + LoadComponentAsync(new ElevatedPrivilegesChecker(), Add); } protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) From a2723f3f579565b2aca0e1c6159362838c9c559b Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 22:37:08 -0400 Subject: [PATCH 238/400] Perform elevated check asynchronously, use a separate function w/ switch statement --- osu.Desktop/OsuGameDesktop.cs | 2 +- .../ElevatedPrivilegesChecker.cs | 48 +++++++++++++------ osu.Desktop/osu.Desktop.csproj | 2 +- 3 files changed, 35 insertions(+), 17 deletions(-) rename osu.Desktop/{Admin => Security}/ElevatedPrivilegesChecker.cs (66%) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 9f7854779e..4a28ab3722 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -9,7 +9,7 @@ using System.Reflection; using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.Win32; -using osu.Desktop.Admin; +using osu.Desktop.Security; using osu.Desktop.Overlays; using osu.Framework.Platform; using osu.Game; diff --git a/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs similarity index 66% rename from osu.Desktop/Admin/ElevatedPrivilegesChecker.cs rename to osu.Desktop/Security/ElevatedPrivilegesChecker.cs index ea4aab0f85..a719dbf952 100644 --- a/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Desktop.Admin +namespace osu.Desktop.Security { /// /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. @@ -21,36 +21,54 @@ namespace osu.Desktop.Admin [Resolved] protected NotificationOverlay Notifications { get; private set; } + private bool elevated; + protected override void LoadComplete() { base.LoadComplete(); - bool elevated = false; - - if (OperatingSystem.IsWindows()) - { - var windowsIdentity = WindowsIdentity.GetCurrent(); - var windowsPrincipal = new WindowsPrincipal(windowsIdentity); - elevated = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); - } - else if (RuntimeInfo.IsUnix) - { - elevated = Mono.Unix.Native.Syscall.geteuid() == 0; - } - if (!elevated) return; Notifications.Post(new ElevatedPrivilegesNotification()); } + [BackgroundDependencyLoader] + private void load() + { + elevated = isElevated(); + } + + private bool isElevated() + { + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.Windows: + { + if (!OperatingSystem.IsWindows()) return false; + + var windowsIdentity = WindowsIdentity.GetCurrent(); + var windowsPrincipal = new WindowsPrincipal(windowsIdentity); + + return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + } + + case RuntimeInfo.Platform.macOS: + case RuntimeInfo.Platform.Linux: + return Mono.Unix.Native.Syscall.geteuid() == 0; + + default: + return false; + } + } + private class ElevatedPrivilegesNotification : SimpleNotification { public override bool IsImportant => true; public ElevatedPrivilegesNotification() { - Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; + Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game as a normal user."; } [BackgroundDependencyLoader] diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 6bd0f64218..ad5c323e9b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -25,7 +25,7 @@ - + From e0f54f58424f6fc77d79fe12ef6f6a511e4b6f71 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 22:51:03 -0400 Subject: [PATCH 239/400] Move load() before LoadComplete() --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index a719dbf952..5fb8263516 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -23,6 +23,12 @@ namespace osu.Desktop.Security private bool elevated; + [BackgroundDependencyLoader] + private void load() + { + elevated = isElevated(); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -33,12 +39,6 @@ namespace osu.Desktop.Security Notifications.Post(new ElevatedPrivilegesNotification()); } - [BackgroundDependencyLoader] - private void load() - { - elevated = isElevated(); - } - private bool isElevated() { switch (RuntimeInfo.OS) From 5a3fbef5ace66e56b2f5312aacf74a4e2ff26793 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Tue, 27 Apr 2021 00:23:08 -0400 Subject: [PATCH 240/400] Use a try-catch, notification activation does nothing --- .../Security/ElevatedPrivilegesChecker.cs | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 5fb8263516..47705eb929 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -19,7 +19,7 @@ namespace osu.Desktop.Security public class ElevatedPrivilegesChecker : Component { [Resolved] - protected NotificationOverlay Notifications { get; private set; } + private NotificationOverlay notifications { get; set; } private bool elevated; @@ -33,32 +33,35 @@ namespace osu.Desktop.Security { base.LoadComplete(); - if (!elevated) - return; - - Notifications.Post(new ElevatedPrivilegesNotification()); + if (elevated) + notifications.Post(new ElevatedPrivilegesNotification()); } private bool isElevated() { - switch (RuntimeInfo.OS) + try { - case RuntimeInfo.Platform.Windows: + switch (RuntimeInfo.OS) { - if (!OperatingSystem.IsWindows()) return false; + case RuntimeInfo.Platform.Windows: + if (!OperatingSystem.IsWindows()) return false; - var windowsIdentity = WindowsIdentity.GetCurrent(); - var windowsPrincipal = new WindowsPrincipal(windowsIdentity); + var windowsIdentity = WindowsIdentity.GetCurrent(); + var windowsPrincipal = new WindowsPrincipal(windowsIdentity); - return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + + case RuntimeInfo.Platform.macOS: + case RuntimeInfo.Platform.Linux: + return Mono.Unix.Native.Syscall.geteuid() == 0; + + default: + return false; } - - case RuntimeInfo.Platform.macOS: - case RuntimeInfo.Platform.Linux: - return Mono.Unix.Native.Syscall.geteuid() == 0; - - default: - return false; + } + catch + { + return false; } } @@ -77,11 +80,7 @@ namespace osu.Desktop.Security Icon = FontAwesome.Solid.ShieldAlt; IconBackgound.Colour = colours.YellowDark; - Activated = delegate - { - notificationOverlay.Hide(); - return true; - }; + Activated = () => true; } } } From ec1c336b0aca43376e853e0a4d0826368924a95d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 13:23:14 +0900 Subject: [PATCH 241/400] Fix a couple of inspections --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 77ea3b05dc..8b20df9a68 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last); lastPoint = last; - return lastPiece?.IsHovered != true; + return lastPiece.IsHovered != true; } private void updateSlider() diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 99cdca045b..337a806b6e 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -3,7 +3,6 @@ using System; using osu.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -35,9 +34,6 @@ namespace osu.Game.Rulesets.Edit public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; - [Resolved(CanBeNull = true)] - private HitObjectComposer composer { get; set; } - protected SelectionBlueprint(HitObject hitObject) { HitObject = hitObject; From 7980d16b4c202bcb063c0569112ad76463e8a9c6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 14:26:12 +0900 Subject: [PATCH 242/400] Add failing test showing the issue of DHO lifetime --- .../Gameplay/TestSceneDrawableHitObject.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs new file mode 100644 index 0000000000..d42802db91 --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Gameplay +{ + [HeadlessTest] + public class TestSceneDrawableHitObject : OsuTestScene + { + [Test] + public void TestEntryLifetime() + { + TestDrawableHitObject dho = null; + var initialHitObject = new HitObject + { + StartTime = 1000 + }; + var entry = new TestLifetimeEntry(new HitObject + { + StartTime = 2000 + }); + + AddStep("Create DHO", () => Child = dho = new TestDrawableHitObject(initialHitObject)); + + AddAssert("Correct initial lifetime", () => dho.LifetimeStart == initialHitObject.StartTime - TestDrawableHitObject.INITIAL_LIFETIME_OFFSET); + + AddStep("Apply entry", () => dho.Apply(entry)); + + AddAssert("Correct initial lifetime", () => dho.LifetimeStart == entry.HitObject.StartTime - TestLifetimeEntry.INITIAL_LIFETIME_OFFSET); + + AddStep("Set lifetime", () => dho.LifetimeEnd = 3000); + AddAssert("Entry lifetime is updated", () => entry.LifetimeEnd == 3000); + } + + private class TestDrawableHitObject : DrawableHitObject + { + public const double INITIAL_LIFETIME_OFFSET = 100; + protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET; + + public TestDrawableHitObject(HitObject hitObject) + : base(hitObject) + { + } + } + + private class TestLifetimeEntry : HitObjectLifetimeEntry + { + public const double INITIAL_LIFETIME_OFFSET = 200; + protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET; + + public TestLifetimeEntry(HitObject hitObject) + : base(hitObject) + { + } + } + } +} From 2303d108bb56b8214c788c18b59424d3fe69858e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:35:14 +0900 Subject: [PATCH 243/400] Simplify false return path --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 47705eb929..92885ad855 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -54,15 +54,13 @@ namespace osu.Desktop.Security case RuntimeInfo.Platform.macOS: case RuntimeInfo.Platform.Linux: return Mono.Unix.Native.Syscall.geteuid() == 0; - - default: - return false; } } catch { - return false; } + + return false; } private class ElevatedPrivilegesNotification : SimpleNotification From 13de571b3c690b2def91c4331b3b70bff00c4719 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:35:57 +0900 Subject: [PATCH 244/400] Rename private method --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 92885ad855..82e7d2a141 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -26,7 +26,7 @@ namespace osu.Desktop.Security [BackgroundDependencyLoader] private void load() { - elevated = isElevated(); + elevated = checkElevated(); } protected override void LoadComplete() @@ -37,7 +37,7 @@ namespace osu.Desktop.Security notifications.Post(new ElevatedPrivilegesNotification()); } - private bool isElevated() + private bool checkElevated() { try { From 2673cd3d9935025232453d9b371d516bc6b7de8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:36:11 +0900 Subject: [PATCH 245/400] Remove unnecessary noop action --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 82e7d2a141..edd4906421 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -77,8 +77,6 @@ namespace osu.Desktop.Security { Icon = FontAwesome.Solid.ShieldAlt; IconBackgound.Colour = colours.YellowDark; - - Activated = () => true; } } } From dbcb1259e2c07a726b8be0b1d12aca362c43df7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:38:19 +0900 Subject: [PATCH 246/400] Add a note about elevated privileges also breaking integrations --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index edd4906421..01458b4c37 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -69,7 +69,7 @@ namespace osu.Desktop.Security public ElevatedPrivilegesNotification() { - Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game as a normal user."; + Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user."; } [BackgroundDependencyLoader] From a2c0951d94102cee2660e65412ffb4c4850114f1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 15:23:33 +0900 Subject: [PATCH 247/400] Use overriding instead of hiding in HitObjectLifetimeEntry Hidden properties are used when the type is the base class. It caused issues when `DrawableHitObject` logic is factored out to `PoolableDrawableWithLifetime` because it is using the base `LifetimeEntry`, not `HitObjectLifetimeEntry`. --- .../Objects/Drawables/DrawableHitObject.cs | 5 ++- .../Objects/HitObjectLifetimeEntry.cs | 41 +++++-------------- 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7739994527..6431ec8980 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; @@ -431,7 +432,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Apply (generally fade-in) transforms leading into the start time. - /// The local drawable hierarchy is recursively delayed to for convenience. + /// The local drawable hierarchy is recursively delayed to for convenience. /// /// By default this will fade in the object from zero with no duration. /// @@ -613,7 +614,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). + /// A more accurate should be set for further optimisation (in , for example). /// /// Only has an effect if this is not being pooled. /// For pooled s, use instead. diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 1954d7e6d2..23e991d00e 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -38,40 +38,19 @@ namespace osu.Game.Rulesets.Objects startTimeBindable.BindValueChanged(onStartTimeChanged, true); } - // The lifetime start, as set by the hitobject. + // The lifetime, as set by the hitobject. private double realLifetimeStart = double.MinValue; - - /// - /// The time at which the should become alive. - /// - public new double LifetimeStart - { - get => realLifetimeStart; - set => setLifetime(realLifetimeStart = value, LifetimeEnd); - } - - // The lifetime end, as set by the hitobject. private double realLifetimeEnd = double.MaxValue; - /// - /// The time at which the should become dead. - /// - public new double LifetimeEnd + public override void SetLifetime(double start, double end) { - get => realLifetimeEnd; - set => setLifetime(LifetimeStart, realLifetimeEnd = value); - } + realLifetimeStart = start; + realLifetimeEnd = end; - private void setLifetime(double start, double end) - { if (keepAlive) - { - start = double.MinValue; - end = double.MaxValue; - } - - base.LifetimeStart = start; - base.LifetimeEnd = end; + base.SetLifetime(double.MinValue, double.MaxValue); + else + base.SetLifetime(start, end); } private bool keepAlive; @@ -87,7 +66,7 @@ namespace osu.Game.Rulesets.Objects return; keepAlive = value; - setLifetime(realLifetimeStart, realLifetimeEnd); + SetLifetime(realLifetimeStart, realLifetimeEnd); } } @@ -98,12 +77,12 @@ namespace osu.Game.Rulesets.Objects /// /// This is only used as an optimisation to delay the initial update of the and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). + /// A more accurate should be set for further optimisation (in , for example). /// protected virtual double InitialLifetimeOffset => 10000; /// - /// Resets according to the change in start time of the . + /// Resets according to the change in start time of the . /// private void onStartTimeChanged(ValueChangedEvent startTime) => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; } From c9e6ca537896fd7a099d2b4a4338b1ecfcda9001 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 15:24:52 +0900 Subject: [PATCH 248/400] Use now-public Entry.SetLifetime method --- .../Objects/Pooling/PoolableDrawableWithLifetime.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 93e476be76..049ff9d446 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -100,11 +100,7 @@ namespace osu.Game.Rulesets.Objects.Pooling base.LifetimeStart = start; base.LifetimeEnd = end; - if (Entry != null) - { - Entry.LifetimeStart = start; - Entry.LifetimeEnd = end; - } + Entry?.SetLifetime(start, end); } private void free() From 3899e500d3edf2b15622ed4e5ace339ab68fbdc0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 17:54:18 +0900 Subject: [PATCH 249/400] Adopt framework change of LifetimeEntry Override SetLifetimeStart/SetLifetimeEnd separately to track individual assignment. It is necessary to ensure real lifetime is not lost when lifetime is partially updated. --- .../Rulesets/Objects/HitObjectLifetimeEntry.cs | 15 +++++++++++++-- .../Pooling/PoolableDrawableWithLifetime.cs | 6 +++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 23e991d00e..0181d6241b 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -42,11 +42,22 @@ namespace osu.Game.Rulesets.Objects private double realLifetimeStart = double.MinValue; private double realLifetimeEnd = double.MaxValue; - public override void SetLifetime(double start, double end) + // This method is called even if `start == LifetimeStart` when `KeepAlive` is true (necessary to update `realLifetimeStart`). + protected override void SetLifetimeStart(double start) { + // This assignment cannot be done in `SetLifetime` because otherwise setting only `LifetimeStart` will make `realLifetimeEnd` to be lost. realLifetimeStart = start; - realLifetimeEnd = end; + base.SetLifetimeStart(start); + } + protected override void SetLifetimeEnd(double end) + { + realLifetimeEnd = end; + base.SetLifetimeEnd(end); + } + + protected override void SetLifetime(double start, double end) + { if (keepAlive) base.SetLifetime(double.MinValue, double.MaxValue); else diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 049ff9d446..93e476be76 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -100,7 +100,11 @@ namespace osu.Game.Rulesets.Objects.Pooling base.LifetimeStart = start; base.LifetimeEnd = end; - Entry?.SetLifetime(start, end); + if (Entry != null) + { + Entry.LifetimeStart = start; + Entry.LifetimeEnd = end; + } } private void free() From 003553aba32d5af6a9decf2a7f2b8c307857ac9b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 18:10:44 +0900 Subject: [PATCH 250/400] Add test of HitObjectLifetimeEntry.KeepAlive behavior --- .../Gameplay/TestSceneDrawableHitObject.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index d42802db91..2e3f192f1b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -37,6 +37,38 @@ namespace osu.Game.Tests.Gameplay AddAssert("Entry lifetime is updated", () => entry.LifetimeEnd == 3000); } + [Test] + public void TestKeepAlive() + { + TestDrawableHitObject dho = null; + TestLifetimeEntry entry = null; + AddStep("Create DHO", () => + { + dho = new TestDrawableHitObject(null); + dho.Apply(entry = new TestLifetimeEntry(new HitObject()) + { + LifetimeStart = 0, + LifetimeEnd = 1000, + }); + Child = dho; + }); + + AddStep("KeepAlive = true", () => entry.KeepAlive = true); + AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue); + + AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500); + AddStep("KeepAlive = false", () => entry.KeepAlive = false); + AddAssert("Lifetime is correct", () => entry.LifetimeStart == 500 && entry.LifetimeEnd == 1000); + + AddStep("Set LifetimeStart while KeepAlive", () => + { + entry.KeepAlive = true; + dho.LifetimeStart = double.MinValue; + entry.KeepAlive = false; + }); + AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000); + } + private class TestDrawableHitObject : DrawableHitObject { public const double INITIAL_LIFETIME_OFFSET = 100; From 3ea55314f2f11c53dc2232e4152beacf7dc7f779 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 27 Apr 2021 11:29:16 +0200 Subject: [PATCH 251/400] Update osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs Co-authored-by: Dan Balasescu --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 679c86072b..e715e4aac6 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -430,9 +430,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Removes all previously applied transforms, then reapplies a new set of transforms with potentially different parameters. - /// The transforms will use the current , and they will use the appropriate start times. - /// This also takes in account potential overrides defined in . + /// Reapplies the current . /// protected void RefreshStateTransforms() => updateState(State.Value, true); From f2e56bd3060438b70105e0cdb4a630f43ea31151 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 15:40:35 +0900 Subject: [PATCH 252/400] Refactor editor selection/blueprint components to be generic --- .../Edit/ManiaBlueprintContainer.cs | 3 +- .../Edit/ManiaSelectionHandler.cs | 7 +- .../Edit/Blueprints/OsuSelectionBlueprint.cs | 2 +- .../Edit/OsuBlueprintContainer.cs | 3 +- .../Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/OsuSelectionHandler.cs | 4 +- .../Edit/TaikoBlueprintContainer.cs | 3 +- .../Edit/TaikoSelectionHandler.cs | 11 +- .../Editing/TestSceneBlueprintSelection.cs | 4 +- .../Editing/TestSceneEditorClipboard.cs | 8 +- .../Editing/TestSceneEditorSelection.cs | 6 +- .../Edit/OverlaySelectionBlueprint.cs | 3 +- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 17 +- .../Compose/Components/BlueprintContainer.cs | 211 +++++---------- .../Components/ComposeBlueprintContainer.cs | 12 +- .../Components/EditorBlueprintContainer.cs | 176 +++++++++++++ .../Components/EditorSelectionHandler.cs | 233 +++++++++++++++++ .../HitObjectOrderedSelectionContainer.cs | 24 +- .../Compose/Components/MoveSelectionEvent.cs | 8 +- .../Compose/Components/SelectionHandler.cs | 247 ++---------------- .../Timeline/TimelineBlueprintContainer.cs | 31 +-- .../Timeline/TimelineHitObjectBlueprint.cs | 22 +- 22 files changed, 588 insertions(+), 449 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs index 2fa3f378ff..c4429176d1 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -4,6 +4,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Screens.Edit.Compose.Components; @@ -30,6 +31,6 @@ namespace osu.Game.Rulesets.Mania.Edit return base.CreateBlueprintFor(hitObject); } - protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 2689ed4112..dd059c967c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -7,12 +7,13 @@ 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.Objects; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaSelectionHandler : SelectionHandler + public class ManiaSelectionHandler : EditorSelectionHandler { [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit [Resolved] private HitObjectComposer composer { get; set; } - public override bool HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Edit return true; } - private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) + private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) { var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index 8dd550bb96..299f8fc43a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints public abstract class OsuSelectionBlueprint : OverlaySelectionBlueprint where T : OsuHitObject { - protected new T HitObject => (T)DrawableObject.HitObject; + protected T HitObject => (T)DrawableObject.HitObject; protected override bool AlwaysShowWhenSelected => true; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index a68ed34e6b..abac5eb56e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; @@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 7b67d7aaf1..806b7e6051 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (b.IsSelected) continue; - var hitObject = (OsuHitObject)b.HitObject; + var hitObject = (OsuHitObject)b.Item; Vector2? snap = checkSnap(hitObject.Position); if (snap == null && hitObject.Position != hitObject.EndPosition) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index de0a4682a3..92f5254182 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuSelectionHandler : SelectionHandler + public class OsuSelectionHandler : EditorSelectionHandler { protected override void OnSelectionChanged() { @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Edit referencePathTypes = null; } - public override bool HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { var hitObjects = selectedMovableObjects; diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs index 8b41448c9d..b1b08a9461 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Edit.Blueprints; using osu.Game.Screens.Edit.Compose.Components; @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Edit { } - protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => new TaikoSelectionBlueprint(hitObject); diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index ac2dd4bdb6..20366e5b3a 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -8,12 +8,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Taiko.Edit { - public class TaikoSelectionHandler : SelectionHandler + public class TaikoSelectionHandler : EditorSelectionHandler { private readonly Bindable selectionRimState = new Bindable(); private readonly Bindable selectionStrongState = new Bindable(); @@ -72,16 +73,16 @@ namespace osu.Game.Rulesets.Taiko.Edit }); } - protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { - if (selection.All(s => s.HitObject is Hit)) + if (selection.All(s => s.Item is Hit)) yield return new TernaryStateMenuItem("Rim") { State = { BindTarget = selectionRimState } }; - if (selection.All(s => s.HitObject is TaikoHitObject)) + if (selection.All(s => s.Item is TaikoHitObject)) yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; } - public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; protected override void UpdateTernaryStates() { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs index fd9c09fd5f..976bf93c15 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs @@ -23,8 +23,8 @@ namespace osu.Game.Tests.Visual.Editing protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); - private BlueprintContainer blueprintContainer - => Editor.ChildrenOfType().First(); + private EditorBlueprintContainer blueprintContainer + => Editor.ChildrenOfType().First(); [Test] public void TestSelectedObjectHasPriorityWhenOverlapping() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 01d9966736..3a063af843 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -132,8 +132,8 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear()); - AddUntilStep("timeline selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); - AddUntilStep("composer selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); + AddUntilStep("timeline selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); + AddUntilStep("composer selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); } AddStep("paste hitobject", () => Editor.Paste()); @@ -142,8 +142,8 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == 2000); - AddUntilStep("timeline selection box is visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha > 0); - AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha > 0); + AddUntilStep("timeline selection box is visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha > 0); + AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha > 0); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs index b82842e30d..0758d73c2a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs @@ -26,15 +26,15 @@ namespace osu.Game.Tests.Visual.Editing protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); - private BlueprintContainer blueprintContainer - => Editor.ChildrenOfType().First(); + private EditorBlueprintContainer blueprintContainer + => Editor.ChildrenOfType().First(); private void moveMouseToObject(Func targetFunc) { AddStep("move mouse to object", () => { var pos = blueprintContainer.SelectionBlueprints - .First(s => s.HitObject == targetFunc()) + .First(s => s.Item == targetFunc()) .ChildrenOfType() .First().ScreenSpaceDrawQuad.Centre; diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs index 75200e3027..6369112d80 100644 --- a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs @@ -3,12 +3,13 @@ using osu.Framework.Graphics.Primitives; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Edit { - public abstract class OverlaySelectionBlueprint : SelectionBlueprint + public abstract class OverlaySelectionBlueprint : SelectionBlueprint { /// /// The which this applies to. diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 337a806b6e..905e433731 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -17,26 +16,26 @@ namespace osu.Game.Rulesets.Edit /// /// A blueprint placed above a adding editing functionality. /// - public abstract class SelectionBlueprint : CompositeDrawable, IStateful + public abstract class SelectionBlueprint : CompositeDrawable, IStateful { - public readonly HitObject HitObject; + public readonly T Item; /// - /// Invoked when this has been selected. + /// Invoked when this has been selected. /// - public event Action Selected; + public event Action> Selected; /// - /// Invoked when this has been deselected. + /// Invoked when this has been deselected. /// - public event Action Deselected; + public event Action> Deselected; public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; - protected SelectionBlueprint(HitObject hitObject) + protected SelectionBlueprint(T item) { - HitObject = hitObject; + Item = item; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index f70e063ba9..a0bb4feadc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -25,37 +23,26 @@ namespace osu.Game.Screens.Edit.Compose.Components { /// /// A container which provides a "blueprint" display of hitobjects. - /// Includes selection and manipulation support via a . + /// Includes selection and manipulation support via a . /// - public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler + public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler { protected DragBox DragBox { get; private set; } - public Container SelectionBlueprints { get; private set; } + public Container> SelectionBlueprints { get; private set; } - protected SelectionHandler SelectionHandler { get; private set; } + protected SelectionHandler SelectionHandler { get; private set; } - protected readonly HitObjectComposer Composer; - - [Resolved(CanBeNull = true)] - private IEditorChangeHandler changeHandler { get; set; } - - [Resolved] - protected EditorClock EditorClock { get; private set; } - - [Resolved] - protected EditorBeatmap Beatmap { get; private set; } - - private readonly BindableList selectedHitObjects = new BindableList(); - private readonly Dictionary blueprintMap = new Dictionary(); + private readonly Dictionary> blueprintMap = new Dictionary>(); [Resolved(canBeNull: true)] private IPositionSnapProvider snapProvider { get; set; } - protected BlueprintContainer(HitObjectComposer composer) - { - Composer = composer; + [Resolved(CanBeNull = true)] + private IEditorChangeHandler changeHandler { get; set; } + protected BlueprintContainer() + { RelativeSizeAxes = Axes.Both; } @@ -73,66 +60,28 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionHandler.CreateProxy(), DragBox.CreateProxy().With(p => p.Depth = float.MinValue) }); - - // For non-pooled rulesets, hitobjects are already present in the playfield which allows the blueprints to be loaded in the async context. - if (Composer != null) - { - foreach (var obj in Composer.HitObjects) - addBlueprintFor(obj.HitObject); - } - - selectedHitObjects.BindTo(Beatmap.SelectedHitObjects); - selectedHitObjects.CollectionChanged += (selectedObjects, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (var o in args.NewItems) - SelectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Select(); - break; - - case NotifyCollectionChangedAction.Remove: - foreach (var o in args.OldItems) - SelectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Deselect(); - - break; - } - }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - Beatmap.HitObjectAdded += addBlueprintFor; - Beatmap.HitObjectRemoved += removeBlueprintFor; - - if (Composer != null) - { - // For pooled rulesets, blueprints must be added for hitobjects already "current" as they would've not been "current" during the async load addition process above. - foreach (var obj in Composer.HitObjects) - addBlueprintFor(obj.HitObject); - - Composer.Playfield.HitObjectUsageBegan += addBlueprintFor; - Composer.Playfield.HitObjectUsageFinished += removeBlueprintFor; - } - } - - protected virtual Container CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; + protected virtual Container> CreateSelectionBlueprintContainer() => new Container> { RelativeSizeAxes = Axes.Both }; /// - /// Creates a which outlines s and handles movement of selections. + /// Creates a which outlines s and handles movement of selections. /// - protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); + protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); /// - /// Creates a for a specific . + /// Creates a for a specific . /// /// The to create the overlay for. - protected virtual SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => null; + protected virtual SelectionBlueprint CreateBlueprintFor(T hitObject) => null; protected virtual DragBox CreateDragBox(Action performSelect) => new DragBox(performSelect); + /// + /// Whether this component is in a state where deselection should be allowed. If false, selection will only be added to. + /// + protected virtual bool AllowDeselection => true; + protected override bool OnMouseDown(MouseDownEvent e) { bool selectionPerformed = performMouseDownActions(e); @@ -143,7 +92,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return selectionPerformed || e.Button == MouseButton.Left; } - private SelectionBlueprint clickedBlueprint; + protected SelectionBlueprint ClickedBlueprint { get; private set; } protected override bool OnClick(ClickEvent e) { @@ -151,11 +100,11 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; // store for double-click handling - clickedBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); + ClickedBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); // Deselection should only occur if no selected blueprints are hovered // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection - if (endClickSelection(e) || clickedBlueprint != null) + if (endClickSelection(e) || ClickedBlueprint != null) return true; deselectAll(); @@ -168,10 +117,9 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; // ensure the blueprint which was hovered for the first click is still the hovered blueprint. - if (clickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint) + if (ClickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != ClickedBlueprint) return false; - EditorClock?.SeekSmoothlyTo(clickedBlueprint.HitObject.StartTime); return true; } @@ -227,9 +175,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (isDraggingBlueprint) { - // handle positional change etc. - foreach (var obj in selectedHitObjects) - Beatmap.Update(obj); + UpdateSelection(); changeHandler?.EndChange(); } @@ -238,6 +184,10 @@ namespace osu.Game.Screens.Edit.Compose.Components DragBox.Hide(); } + protected virtual void UpdateSelection() + { + } + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) @@ -258,7 +208,7 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (action.ActionType) { case PlatformActionType.SelectAll: - selectAll(); + SelectAll(); return true; } @@ -271,61 +221,55 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Blueprint Addition/Removal - private void addBlueprintFor(HitObject hitObject) + protected virtual void AddBlueprintFor(T item) { - if (hitObject is IBarLine) + if (blueprintMap.ContainsKey(item)) return; - if (blueprintMap.ContainsKey(hitObject)) - return; - - var blueprint = CreateBlueprintFor(hitObject); + var blueprint = CreateBlueprintFor(item); if (blueprint == null) return; - blueprintMap[hitObject] = blueprint; + blueprintMap[item] = blueprint; - blueprint.Selected += onBlueprintSelected; - blueprint.Deselected += onBlueprintDeselected; - - if (Beatmap.SelectedHitObjects.Contains(hitObject)) - blueprint.Select(); + blueprint.Selected += OnBlueprintSelected; + blueprint.Deselected += OnBlueprintDeselected; SelectionBlueprints.Add(blueprint); - OnBlueprintAdded(hitObject); + OnBlueprintAdded(blueprint); } - private void removeBlueprintFor(HitObject hitObject) + protected void RemoveBlueprintFor(T item) { - if (!blueprintMap.Remove(hitObject, out var blueprint)) + if (!blueprintMap.Remove(item, out var blueprint)) return; blueprint.Deselect(); - blueprint.Selected -= onBlueprintSelected; - blueprint.Deselected -= onBlueprintDeselected; + blueprint.Selected -= OnBlueprintSelected; + blueprint.Deselected -= OnBlueprintDeselected; SelectionBlueprints.Remove(blueprint); if (movementBlueprints?.Contains(blueprint) == true) finishSelectionMovement(); - OnBlueprintRemoved(hitObject); + OnBlueprintRemoved(blueprint); } /// /// Called after a blueprint has been added. /// - /// The for which the blueprint has been added. - protected virtual void OnBlueprintAdded(HitObject hitObject) + /// The for which the blueprint has been added. + protected virtual void OnBlueprintAdded(SelectionBlueprint blueprint) { } /// /// Called after a blueprint has been removed. /// - /// The for which the blueprint has been removed. - protected virtual void OnBlueprintRemoved(HitObject hitObject) + /// The for which the blueprint has been removed. + protected virtual void OnBlueprintRemoved(SelectionBlueprint item) { } @@ -347,7 +291,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Iterate from the top of the input stack (blueprints closest to the front of the screen first). // Priority is given to already-selected blueprints. - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) { if (!blueprint.IsHovered) continue; @@ -371,7 +315,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Iterate from the top of the input stack (blueprints closest to the front of the screen first). // Priority is given to already-selected blueprints. - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) { if (!blueprint.IsHovered) continue; @@ -405,7 +349,7 @@ namespace osu.Game.Screens.Edit.Compose.Components 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()) + if (AllowDeselection && !isValidForSelection()) blueprint.Deselect(); break; } @@ -413,35 +357,29 @@ namespace osu.Game.Screens.Edit.Compose.Components } /// - /// Selects all s. + /// Selects all s. /// - private void selectAll() + protected virtual void SelectAll() { - Composer.Playfield.KeepAllAlive(); - // Scheduled to allow the change in lifetime to take place. Schedule(() => SelectionBlueprints.ToList().ForEach(m => m.Select())); } /// - /// Deselects all selected s. + /// Deselects all selected s. /// private void deselectAll() => SelectionHandler.SelectedBlueprints.ToList().ForEach(m => m.Deselect()); - private void onBlueprintSelected(SelectionBlueprint blueprint) + protected virtual void OnBlueprintSelected(SelectionBlueprint blueprint) { SelectionHandler.HandleSelected(blueprint); SelectionBlueprints.ChangeChildDepth(blueprint, 1); - - Composer.Playfield.SetKeepAlive(blueprint.HitObject, true); } - private void onBlueprintDeselected(SelectionBlueprint blueprint) + protected virtual void OnBlueprintDeselected(SelectionBlueprint blueprint) { SelectionBlueprints.ChangeChildDepth(blueprint, 0); SelectionHandler.HandleDeselected(blueprint); - - Composer.Playfield.SetKeepAlive(blueprint.HitObject, false); } #endregion @@ -449,7 +387,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection Movement private Vector2[] movementBlueprintOriginalPositions; - private SelectionBlueprint[] movementBlueprints; + private SelectionBlueprint[] movementBlueprints; private bool isDraggingBlueprint; /// @@ -466,10 +404,12 @@ namespace osu.Game.Screens.Edit.Compose.Components return; // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject - movementBlueprints = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).ToArray(); + movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); } + protected virtual IEnumerable> SortForMovement(IReadOnlyList> blueprints) => blueprints; + /// /// Moves the current selected blueprints. /// @@ -497,7 +437,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (positionalResult.ScreenSpacePosition == testPosition) continue; // attempt to move the objects, and abort any time based snapping if we can. - if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) return true; } @@ -510,20 +450,12 @@ namespace osu.Game.Screens.Edit.Compose.Components // Retrieve a snapped position. var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); - // Move the hitobjects. - if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), result.ScreenSpacePosition))) - return true; + return ApplySnapResult(movementBlueprints, result); + } - if (result.Time.HasValue) - { - // Apply the start time at the newly snapped-to position - double offset = result.Time.Value - movementBlueprints.First().HitObject.StartTime; - - if (offset != 0) - Beatmap.PerformOnSelection(obj => obj.StartTime += offset); - } - - return true; + protected virtual bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) + { + return !SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition)); } /// @@ -542,22 +474,5 @@ namespace osu.Game.Screens.Edit.Compose.Components } #endregion - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (Beatmap != null) - { - Beatmap.HitObjectAdded -= addBlueprintFor; - Beatmap.HitObjectRemoved -= removeBlueprintFor; - } - - if (Composer != null) - { - Composer.Playfield.HitObjectUsageBegan -= addBlueprintFor; - Composer.Playfield.HitObjectUsageFinished -= removeBlueprintFor; - } - } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index b0a6a091f0..994e862946 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -27,12 +27,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A blueprint container generally displayed as an overlay to a ruleset's playfield. /// - public class ComposeBlueprintContainer : BlueprintContainer + public class ComposeBlueprintContainer : EditorBlueprintContainer { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; private readonly Container placementBlueprintContainer; + protected new EditorSelectionHandler SelectionHandler => (EditorSelectionHandler)base.SelectionHandler; + private PlacementBlueprint currentPlacement; private InputManager inputManager; @@ -113,7 +115,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // convert to game space coordinates delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero); - SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, firstBlueprint.ScreenSpaceSelectionPoint + delta)); + SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, firstBlueprint.ScreenSpaceSelectionPoint + delta)); } private void updatePlacementNewCombo() @@ -237,7 +239,7 @@ namespace osu.Game.Screens.Edit.Compose.Components updatePlacementPosition(); } - protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) + protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) { var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); @@ -249,9 +251,9 @@ namespace osu.Game.Screens.Edit.Compose.Components public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; - protected override void OnBlueprintAdded(HitObject hitObject) + protected override void OnBlueprintAdded(SelectionBlueprint item) { - base.OnBlueprintAdded(hitObject); + base.OnBlueprintAdded(item); refreshTool(); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs new file mode 100644 index 0000000000..aef02d5ffd --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -0,0 +1,176 @@ +// 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.Collections.Specialized; +using System.Linq; +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.Rulesets.Edit; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class EditorBlueprintContainer : BlueprintContainer + { + [Resolved] + protected EditorClock EditorClock { get; private set; } + + [Resolved] + protected EditorBeatmap Beatmap { get; private set; } + + protected readonly HitObjectComposer Composer; + + private readonly BindableList selectedHitObjects = new BindableList(); + + protected EditorBlueprintContainer(HitObjectComposer composer) + { + Composer = composer; + } + + [BackgroundDependencyLoader] + private void load() + { + // For non-pooled rulesets, hitobjects are already present in the playfield which allows the blueprints to be loaded in the async context. + if (Composer != null) + { + foreach (var obj in Composer.HitObjects) + AddBlueprintFor(obj.HitObject); + } + + selectedHitObjects.BindTo(Beatmap.SelectedHitObjects); + selectedHitObjects.CollectionChanged += (selectedObjects, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (var o in args.NewItems) + SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Select(); + break; + + case NotifyCollectionChangedAction.Remove: + foreach (var o in args.OldItems) + SelectionBlueprints.FirstOrDefault(b => b.Item == o)?.Deselect(); + + break; + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Beatmap.HitObjectAdded += AddBlueprintFor; + Beatmap.HitObjectRemoved += RemoveBlueprintFor; + + if (Composer != null) + { + // For pooled rulesets, blueprints must be added for hitobjects already "current" as they would've not been "current" during the async load addition process above. + foreach (var obj in Composer.HitObjects) + AddBlueprintFor(obj.HitObject); + + Composer.Playfield.HitObjectUsageBegan += AddBlueprintFor; + Composer.Playfield.HitObjectUsageFinished += RemoveBlueprintFor; + } + } + + protected override IEnumerable> SortForMovement(IReadOnlyList> blueprints) + => blueprints.OrderBy(b => b.Item.StartTime); + + protected override bool AllowDeselection => !EditorClock.IsRunning; + + protected override bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) + { + if (!base.ApplySnapResult(blueprints, result)) + return false; + + if (result.Time.HasValue) + { + // Apply the start time at the newly snapped-to position + double offset = result.Time.Value - blueprints.First().Item.StartTime; + + if (offset != 0) + Beatmap.PerformOnSelection(obj => obj.StartTime += offset); + } + + return true; + } + + protected override void AddBlueprintFor(HitObject item) + { + if (item is IBarLine) + return; + + base.AddBlueprintFor(item); + } + + protected override void OnBlueprintAdded(SelectionBlueprint blueprint) + { + base.OnBlueprintAdded(blueprint); + if (Beatmap.SelectedHitObjects.Contains(blueprint.Item)) + blueprint.Select(); + } + + protected override void UpdateSelection() + { + base.UpdateSelection(); + + // handle positional change etc. + foreach (var blueprint in SelectionBlueprints) + Beatmap.Update(blueprint.Item); + } + + protected override bool OnDoubleClick(DoubleClickEvent e) + { + if (!base.OnDoubleClick(e)) + return false; + + EditorClock?.SeekSmoothlyTo(ClickedBlueprint.Item.StartTime); + return true; + } + + protected override Container> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; + + protected override void SelectAll() + { + Composer.Playfield.KeepAllAlive(); + + base.SelectAll(); + } + + protected override void OnBlueprintSelected(SelectionBlueprint blueprint) + { + base.OnBlueprintSelected(blueprint); + + Composer.Playfield.SetKeepAlive(blueprint.Item, true); + } + + protected override void OnBlueprintDeselected(SelectionBlueprint blueprint) + { + base.OnBlueprintDeselected(blueprint); + + Composer.Playfield.SetKeepAlive(blueprint.Item, false); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (Beatmap != null) + { + Beatmap.HitObjectAdded -= AddBlueprintFor; + Beatmap.HitObjectRemoved -= RemoveBlueprintFor; + } + + if (Composer != null) + { + Composer.Playfield.HitObjectUsageBegan -= AddBlueprintFor; + Composer.Playfield.HitObjectUsageFinished -= RemoveBlueprintFor; + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs new file mode 100644 index 0000000000..a117d42574 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.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; +using System.Collections.Generic; +using System.Linq; +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Audio; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class EditorSelectionHandler : SelectionHandler, IHasContextMenu + { + [Resolved] + protected EditorBeatmap EditorBeatmap { get; private set; } + + [BackgroundDependencyLoader] + private void load() + { + createStateBindables(); + + // bring in updates from selection changes + EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); + EditorBeatmap.SelectedHitObjects.BindTo(SelectedItems); + + SelectedItems.CollectionChanged += (sender, args) => + { + Scheduler.AddOnce(UpdateTernaryStates); + }; + } + + internal override void HandleSelected(SelectionBlueprint blueprint) + { + base.HandleSelected(blueprint); + + // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. + if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.Item)) + EditorBeatmap.SelectedHitObjects.Add(blueprint.Item); + } + + internal override void HandleDeselected(SelectionBlueprint blueprint) + { + base.HandleDeselected(blueprint); + + EditorBeatmap.SelectedHitObjects.Remove(blueprint.Item); + } + + protected override void DeleteItems(IEnumerable items) => EditorBeatmap.RemoveRange(items); + + #region Selection State + + /// + /// The state of "new combo" for all selected hitobjects. + /// + public readonly Bindable SelectionNewComboState = new Bindable(); + + /// + /// The state of each sample type for all selected hitobjects. Keys match with constant specifications. + /// + public readonly Dictionary> SelectionSampleStates = new Dictionary>(); + + /// + /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) + /// + private void createStateBindables() + { + foreach (var sampleName in HitSampleInfo.AllAdditions) + { + var bindable = new Bindable + { + Description = sampleName.Replace("hit", string.Empty).Titleize() + }; + + bindable.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + RemoveHitSample(sampleName); + break; + + case TernaryState.True: + AddHitSample(sampleName); + break; + } + }; + + SelectionSampleStates[sampleName] = bindable; + } + + // new combo + SelectionNewComboState.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + SetNewCombo(false); + break; + + case TernaryState.True: + SetNewCombo(true); + break; + } + }; + } + + /// + /// Called when context menu ternary states may need to be recalculated (selection changed or hitobject updated). + /// + protected virtual void UpdateTernaryStates() + { + SelectionNewComboState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.NewCombo); + + foreach (var (sampleName, bindable) in SelectionSampleStates) + { + bindable.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); + } + } + + /// + /// Given a selection target and a function of truth, retrieve the correct ternary state for display. + /// + protected TernaryState GetStateFromSelection(IEnumerable selection, Func func) + { + if (selection.Any(func)) + return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; + + return TernaryState.False; + } + + #endregion + + #region Sample Changes + + /// + /// Adds a hit sample to all selected s. + /// + /// The name of the hit sample. + public void AddHitSample(string sampleName) + { + EditorBeatmap.PerformOnSelection(h => + { + // Make sure there isn't already an existing sample + if (h.Samples.Any(s => s.Name == sampleName)) + return; + + h.Samples.Add(new HitSampleInfo(sampleName)); + }); + } + + /// + /// Set the new combo state of all selected s. + /// + /// Whether to set or unset. + /// Throws if any selected object doesn't implement + public void SetNewCombo(bool state) + { + EditorBeatmap.PerformOnSelection(h => + { + var comboInfo = h as IHasComboInformation; + + if (comboInfo == null || comboInfo.NewCombo == state) return; + + comboInfo.NewCombo = state; + EditorBeatmap.Update(h); + }); + } + + /// + /// Removes a hit sample from all selected s. + /// + /// The name of the hit sample. + public void RemoveHitSample(string sampleName) + { + EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName)); + } + + #endregion + + #region Context Menu + + public MenuItem[] ContextMenuItems + { + get + { + if (!SelectedBlueprints.Any(b => b.IsHovered)) + return Array.Empty(); + + var items = new List(); + + items.AddRange(GetContextMenuItemsForSelection(SelectedBlueprints)); + + if (SelectedBlueprints.All(b => b.Item is IHasComboInformation)) + { + items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }); + } + + if (SelectedBlueprints.Count == 1) + items.AddRange(SelectedBlueprints[0].ContextMenuItems); + + items.AddRange(new[] + { + new OsuMenuItem("Sound") + { + Items = SelectionSampleStates.Select(kvp => + new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() + }, + new OsuMenuItem("Delete", MenuItemType.Destructive, DeleteSelected), + }); + + 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(); + + #endregion + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index d612cf3fe0..4078661a26 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -11,17 +11,17 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A container for ordered by their start times. + /// A container for ordered by their start times. /// - public sealed class HitObjectOrderedSelectionContainer : Container + public sealed class HitObjectOrderedSelectionContainer : Container> { - public override void Add(SelectionBlueprint drawable) + public override void Add(SelectionBlueprint drawable) { base.Add(drawable); bindStartTime(drawable); } - public override bool Remove(SelectionBlueprint drawable) + public override bool Remove(SelectionBlueprint drawable) { if (!base.Remove(drawable)) return false; @@ -36,11 +36,11 @@ namespace osu.Game.Screens.Edit.Compose.Components unbindAllStartTimes(); } - private readonly Dictionary startTimeMap = new Dictionary(); + private readonly Dictionary, IBindable> startTimeMap = new Dictionary, IBindable>(); - private void bindStartTime(SelectionBlueprint blueprint) + private void bindStartTime(SelectionBlueprint blueprint) { - var bindable = blueprint.HitObject.StartTimeBindable.GetBoundCopy(); + var bindable = blueprint.Item.StartTimeBindable.GetBoundCopy(); bindable.BindValueChanged(_ => { @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components startTimeMap[blueprint] = bindable; } - private void unbindStartTime(SelectionBlueprint blueprint) + private void unbindStartTime(SelectionBlueprint blueprint) { startTimeMap[blueprint].UnbindAll(); startTimeMap.Remove(blueprint); @@ -66,16 +66,16 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override int Compare(Drawable x, Drawable y) { - var xObj = (SelectionBlueprint)x; - var yObj = (SelectionBlueprint)y; + var xObj = (SelectionBlueprint)x; + var yObj = (SelectionBlueprint)y; // Put earlier blueprints towards the end of the list, so they handle input first - int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); + int i = yObj.Item.StartTime.CompareTo(xObj.Item.StartTime); if (i != 0) return i; // Fall back to end time if the start time is equal. - i = yObj.HitObject.GetEndTime().CompareTo(xObj.HitObject.GetEndTime()); + i = yObj.Item.GetEndTime().CompareTo(xObj.Item.GetEndTime()); return i == 0 ? CompareReverseChildID(y, x) : i; } diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index 0792d0f80e..a07b434011 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -9,12 +9,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// An event which occurs when a is moved. /// - public class MoveSelectionEvent + public class MoveSelectionEvent { /// - /// The that triggered this . + /// The that triggered this . /// - public readonly SelectionBlueprint Blueprint; + public readonly SelectionBlueprint Blueprint; /// /// The expected screen-space position of the hitobject at the current cursor position. @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public readonly Vector2 InstantDelta; - public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpacePosition) + public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpacePosition) { Blueprint = blueprint; ScreenSpacePosition = screenSpacePosition; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index b06e982859..1ee1de7d43 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -4,25 +4,19 @@ using System; using System.Collections.Generic; using System.Linq; -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osuTK; using osuTK.Input; @@ -31,16 +25,18 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines s and handles movement of selections. /// - public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu + public class SelectionHandler : CompositeDrawable, IKeyBindingHandler { /// /// The currently selected blueprints. /// Should be used when operations are dealing directly with the visible blueprints. /// For more general selection operations, use instead. /// - public IEnumerable SelectedBlueprints => selectedBlueprints; + public IReadOnlyList> SelectedBlueprints => selectedBlueprints; - private readonly List selectedBlueprints; + protected BindableList SelectedItems = new BindableList(); + + private readonly List> selectedBlueprints; private Drawable content; @@ -48,15 +44,12 @@ namespace osu.Game.Screens.Edit.Compose.Components protected SelectionBox SelectionBox { get; private set; } - [Resolved] - protected EditorBeatmap EditorBeatmap { get; private set; } - [Resolved(CanBeNull = true)] protected IEditorChangeHandler ChangeHandler { get; private set; } public SelectionHandler() { - selectedBlueprints = new List(); + selectedBlueprints = new List>(); RelativeSizeAxes = Axes.Both; AlwaysPresent = true; @@ -66,8 +59,6 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { - createStateBindables(); - InternalChild = content = new Container { Children = new Drawable[] @@ -95,6 +86,11 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionBox = CreateSelectionBox(), } }; + + SelectedItems.CollectionChanged += (sender, args) => + { + Scheduler.AddOnce(updateVisibility); + }; } public SelectionBox CreateSelectionBox() @@ -139,7 +135,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be moved. /// Returning true will also propagate StartTime changes provided by the closest . /// - public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; + public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; /// /// Handles the selected s being rotated. @@ -174,7 +170,7 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (action.ActionMethod) { case PlatformActionMethod.Delete: - deleteSelected(); + DeleteSelected(); return true; } @@ -198,24 +194,18 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handle a blueprint becoming selected. /// /// The blueprint. - internal void HandleSelected(SelectionBlueprint blueprint) + internal virtual void HandleSelected(SelectionBlueprint blueprint) { selectedBlueprints.Add(blueprint); - - // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. - if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.HitObject)) - EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); } /// /// Handle a blueprint becoming deselected. /// /// The blueprint. - internal void HandleDeselected(SelectionBlueprint blueprint) + internal virtual void HandleDeselected(SelectionBlueprint blueprint) { selectedBlueprints.Remove(blueprint); - - EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); } /// @@ -224,7 +214,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The mouse event responsible for selection. /// Whether a selection was performed. - internal bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) + internal bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { if (e.ShiftPressed && e.Button == MouseButton.Right) { @@ -248,7 +238,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The mouse event responsible for deselection. /// Whether a deselection was performed. - internal bool MouseUpSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) + internal bool MouseUpSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { if (blueprint.IsSelected) { @@ -259,15 +249,19 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - private void handleQuickDeletion(SelectionBlueprint blueprint) + private void handleQuickDeletion(SelectionBlueprint blueprint) { if (blueprint.HandleQuickDeletion()) return; if (!blueprint.IsSelected) - EditorBeatmap.Remove(blueprint.HitObject); + DeleteItems(new[] { blueprint.Item }); else - deleteSelected(); + DeleteSelected(); + } + + protected virtual void DeleteItems(IEnumerable items) + { } /// @@ -275,7 +269,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The blueprint to select. /// Whether selection state was changed. - private bool ensureSelected(SelectionBlueprint blueprint) + private bool ensureSelected(SelectionBlueprint blueprint) { if (blueprint.IsSelected) return false; @@ -285,9 +279,9 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } - private void deleteSelected() + protected void DeleteSelected() { - EditorBeatmap.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); + DeleteItems(selectedBlueprints.Select(b => b.Item)); } #endregion @@ -295,11 +289,11 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Outline Display /// - /// Updates whether this is visible. + /// Updates whether this is visible. /// private void updateVisibility() { - int count = EditorBeatmap.SelectedHitObjects.Count; + int count = SelectedItems.Count; selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty; @@ -340,188 +334,5 @@ namespace osu.Game.Screens.Edit.Compose.Components } #endregion - - #region Sample Changes - - /// - /// Adds a hit sample to all selected s. - /// - /// The name of the hit sample. - public void AddHitSample(string sampleName) - { - EditorBeatmap.PerformOnSelection(h => - { - // Make sure there isn't already an existing sample - if (h.Samples.Any(s => s.Name == sampleName)) - return; - - h.Samples.Add(new HitSampleInfo(sampleName)); - }); - } - - /// - /// Set the new combo state of all selected s. - /// - /// Whether to set or unset. - /// Throws if any selected object doesn't implement - public void SetNewCombo(bool state) - { - EditorBeatmap.PerformOnSelection(h => - { - var comboInfo = h as IHasComboInformation; - - if (comboInfo == null || comboInfo.NewCombo == state) return; - - comboInfo.NewCombo = state; - EditorBeatmap.Update(h); - }); - } - - /// - /// Removes a hit sample from all selected s. - /// - /// The name of the hit sample. - public void RemoveHitSample(string sampleName) - { - EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName)); - } - - #endregion - - #region Selection State - - /// - /// The state of "new combo" for all selected hitobjects. - /// - public readonly Bindable SelectionNewComboState = new Bindable(); - - /// - /// The state of each sample type for all selected hitobjects. Keys match with constant specifications. - /// - public readonly Dictionary> SelectionSampleStates = new Dictionary>(); - - /// - /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) - /// - private void createStateBindables() - { - foreach (var sampleName in HitSampleInfo.AllAdditions) - { - var bindable = new Bindable - { - Description = sampleName.Replace("hit", string.Empty).Titleize() - }; - - bindable.ValueChanged += state => - { - switch (state.NewValue) - { - case TernaryState.False: - RemoveHitSample(sampleName); - break; - - case TernaryState.True: - AddHitSample(sampleName); - break; - } - }; - - SelectionSampleStates[sampleName] = bindable; - } - - // new combo - SelectionNewComboState.ValueChanged += state => - { - switch (state.NewValue) - { - case TernaryState.False: - SetNewCombo(false); - break; - - case TernaryState.True: - SetNewCombo(true); - break; - } - }; - - // bring in updates from selection changes - EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); - EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => - { - Scheduler.AddOnce(updateVisibility); - Scheduler.AddOnce(UpdateTernaryStates); - }; - } - - /// - /// Called when context menu ternary states may need to be recalculated (selection changed or hitobject updated). - /// - protected virtual void UpdateTernaryStates() - { - SelectionNewComboState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.NewCombo); - - foreach (var (sampleName, bindable) in SelectionSampleStates) - { - bindable.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); - } - } - - /// - /// Given a selection target and a function of truth, retrieve the correct ternary state for display. - /// - protected TernaryState GetStateFromSelection(IEnumerable selection, Func func) - { - if (selection.Any(func)) - return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; - - return TernaryState.False; - } - - #endregion - - #region Context Menu - - public MenuItem[] ContextMenuItems - { - get - { - if (!selectedBlueprints.Any(b => b.IsHovered)) - return Array.Empty(); - - var items = new List(); - - items.AddRange(GetContextMenuItemsForSelection(selectedBlueprints)); - - if (selectedBlueprints.All(b => b.HitObject is IHasComboInformation)) - { - items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }); - } - - if (selectedBlueprints.Count == 1) - items.AddRange(selectedBlueprints[0].ContextMenuItems); - - items.AddRange(new[] - { - new OsuMenuItem("Sound") - { - Items = SelectionSampleStates.Select(kvp => - new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() - }, - new OsuMenuItem("Delete", MenuItemType.Destructive, deleteSelected), - }); - - 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(); - - #endregion } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 3555bc2800..2bb80bc27b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -25,20 +25,17 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - internal class TimelineBlueprintContainer : BlueprintContainer + internal class TimelineBlueprintContainer : EditorBlueprintContainer { [Resolved(CanBeNull = true)] private Timeline timeline { get; set; } - [Resolved] - private EditorBeatmap beatmap { get; set; } - [Resolved] private OsuColour colours { get; set; } private DragEvent lastDragEvent; private Bindable placement; - private SelectionBlueprint placementBlueprint; + private SelectionBlueprint placementBlueprint; private SelectableAreaBackground backgroundBox; @@ -76,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline base.LoadComplete(); DragBox.Alpha = 0; - placement = beatmap.PlacementObject.GetBoundCopy(); + placement = Beatmap.PlacementObject.GetBoundCopy(); placement.ValueChanged += placementChanged; } @@ -100,7 +97,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override Container CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override Container> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; protected override bool OnHover(HoverEvent e) { @@ -160,7 +157,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // remove objects from the stack as long as their end time is in the past. while (currentConcurrentObjects.TryPeek(out HitObject hitObject)) { - if (Precision.AlmostBigger(hitObject.GetEndTime(), b.HitObject.StartTime, 1)) + if (Precision.AlmostBigger(hitObject.GetEndTime(), b.Item.StartTime, 1)) break; currentConcurrentObjects.Pop(); @@ -168,7 +165,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the stack gets too high, we should have space below it to display the next batch of objects. // importantly, we only do this if time has incremented, else a stack of hitobjects all at the same time value would start to overlap themselves. - if (currentConcurrentObjects.TryPeek(out HitObject h) && !Precision.AlmostEquals(h.StartTime, b.HitObject.StartTime, 1)) + if (currentConcurrentObjects.TryPeek(out HitObject h) && !Precision.AlmostEquals(h.StartTime, b.Item.StartTime, 1)) { if (currentConcurrentObjects.Count >= stack_reset_count) currentConcurrentObjects.Clear(); @@ -176,13 +173,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline b.Y = -(stack_offset * currentConcurrentObjects.Count); - currentConcurrentObjects.Push(b.HitObject); + currentConcurrentObjects.Push(b.Item); } } - protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); - protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) + protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) { return new TimelineHitObjectBlueprint(hitObject) { @@ -239,10 +236,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - internal class TimelineSelectionHandler : SelectionHandler, IKeyBindingHandler + internal class TimelineSelectionHandler : EditorSelectionHandler, IKeyBindingHandler { // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation - public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; public bool OnPressed(GlobalAction action) { @@ -344,13 +341,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected class TimelineSelectionBlueprintContainer : Container + protected class TimelineSelectionBlueprintContainer : Container> { - protected override Container Content { get; } + protected override Container> Content { get; } public TimelineSelectionBlueprintContainer() { - AddInternal(new TimelinePart(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); + AddInternal(new TimelinePart>(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 0425370ae5..dbe689be2f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -26,7 +26,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class TimelineHitObjectBlueprint : SelectionBlueprint + public class TimelineHitObjectBlueprint : SelectionBlueprint { private const float circle_size = 38; @@ -49,13 +49,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private ISkinSource skin { get; set; } - public TimelineHitObjectBlueprint(HitObject hitObject) - : base(hitObject) + public TimelineHitObjectBlueprint(HitObject item) + : base(item) { Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; - startTime = hitObject.StartTimeBindable.GetBoundCopy(); + startTime = item.StartTimeBindable.GetBoundCopy(); startTime.BindValueChanged(time => X = (float)time.NewValue, true); RelativePositionAxes = Axes.X; @@ -95,9 +95,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, }); - if (hitObject is IHasDuration) + if (item is IHasDuration) { - colouredComponents.Add(new DragArea(hitObject) + colouredComponents.Add(new DragArea(item) { OnDragHandled = e => OnDragHandled?.Invoke(e) }); @@ -108,7 +108,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); - if (HitObject is IHasComboInformation comboInfo) + if (Item is IHasComboInformation comboInfo) { indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy(); indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true); @@ -136,7 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateComboColour() { - if (!(HitObject is IHasComboInformation combo)) + if (!(Item is IHasComboInformation combo)) return; var comboColours = skin.GetConfig>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty(); @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline border.Hide(); } - if (HitObject is IHasDuration duration && duration.Duration > 0) + if (Item is IHasDuration duration && duration.Duration > 0) circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f)); else circle.Colour = comboColour; @@ -166,14 +166,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline base.Update(); // no bindable so we perform this every update - float duration = (float)(HitObject.GetEndTime() - HitObject.StartTime); + float duration = (float)(Item.GetEndTime() - Item.StartTime); if (Width != duration) { Width = duration; // kind of haphazard but yeah, no bindables. - if (HitObject is IHasRepeats repeats) + if (Item is IHasRepeats repeats) updateRepeats(repeats); } } From eac139ca0e0a1747199c3ac1110a434d25fa5429 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 17:41:46 +0900 Subject: [PATCH 253/400] Allow BlueprintContainer to perform movement without an `ISnapProvider` --- .../Compose/Components/BlueprintContainer.cs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index a0bb4feadc..bd22c7329a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -420,25 +420,25 @@ namespace osu.Game.Screens.Edit.Compose.Components if (movementBlueprints == null) return false; - if (snapProvider == null) - return true; - Debug.Assert(movementBlueprintOriginalPositions != null); Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; - // check for positional snap for every object in selection (for things like object-object snapping) - for (var i = 0; i < movementBlueprintOriginalPositions.Length; i++) + if (snapProvider != null) { - var testPosition = movementBlueprintOriginalPositions[i] + distanceTravelled; + // check for positional snap for every object in selection (for things like object-object snapping) + for (var i = 0; i < movementBlueprintOriginalPositions.Length; i++) + { + var testPosition = movementBlueprintOriginalPositions[i] + distanceTravelled; - var positionalResult = snapProvider.SnapScreenSpacePositionToValidPosition(testPosition); + var positionalResult = snapProvider.SnapScreenSpacePositionToValidPosition(testPosition); - if (positionalResult.ScreenSpacePosition == testPosition) continue; + if (positionalResult.ScreenSpacePosition == testPosition) continue; - // attempt to move the objects, and abort any time based snapping if we can. - if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) - return true; + // attempt to move the objects, and abort any time based snapping if we can. + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) + return true; + } } // if no positional snapping could be performed, try unrestricted snapping from the earliest @@ -448,15 +448,18 @@ namespace osu.Game.Screens.Edit.Compose.Components Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled; // Retrieve a snapped position. - var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); + var result = snapProvider?.SnapScreenSpacePositionToValidTime(movePosition); + + if (result == null) + { + return SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), movePosition)); + } return ApplySnapResult(movementBlueprints, result); } - protected virtual bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) - { - return !SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition)); - } + protected virtual bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) => + SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition)); /// /// Finishes the current movement of selected blueprints. From 32416e4e313eb818464355124415d661e23b8b2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 17:42:10 +0900 Subject: [PATCH 254/400] Move model selection handling to base `SelectionHandler` class --- .../Compose/Components/EditorSelectionHandler.cs | 16 ---------------- .../Edit/Compose/Components/SelectionHandler.cs | 5 +++++ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a117d42574..4c6833cc4a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -37,22 +37,6 @@ namespace osu.Game.Screens.Edit.Compose.Components }; } - internal override void HandleSelected(SelectionBlueprint blueprint) - { - base.HandleSelected(blueprint); - - // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. - if (!EditorBeatmap.SelectedHitObjects.Contains(blueprint.Item)) - EditorBeatmap.SelectedHitObjects.Add(blueprint.Item); - } - - internal override void HandleDeselected(SelectionBlueprint blueprint) - { - base.HandleDeselected(blueprint); - - EditorBeatmap.SelectedHitObjects.Remove(blueprint.Item); - } - protected override void DeleteItems(IEnumerable items) => EditorBeatmap.RemoveRange(items); #region Selection State diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 1ee1de7d43..59ade746a9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -196,6 +196,10 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal virtual void HandleSelected(SelectionBlueprint blueprint) { + // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. + if (!SelectedItems.Contains(blueprint.Item)) + SelectedItems.Add(blueprint.Item); + selectedBlueprints.Add(blueprint); } @@ -205,6 +209,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal virtual void HandleDeselected(SelectionBlueprint blueprint) { + SelectedItems.Remove(blueprint.Item); selectedBlueprints.Remove(blueprint); } From f586bc46e624257e8e8c8438c644d4dfdcb240d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:03:34 +0900 Subject: [PATCH 255/400] Avoid using `EditorBeatmap.SelectedHitObjects` --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 92f5254182..5164c7f204 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -374,8 +374,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// All osu! hitobjects which can be moved/rotated/scaled. /// - private OsuHitObject[] selectedMovableObjects => EditorBeatmap.SelectedHitObjects - .OfType() + private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType() .Where(h => !(h is Spinner)) .ToArray(); From dd3d8e5d0388974191d4907e449814ec25b1576c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:06:27 +0900 Subject: [PATCH 256/400] Make `SelectionHandler` abstract to ensure things get implemented --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +-- .../Edit/Compose/Components/BlueprintContainer.cs | 2 +- .../Compose/Components/EditorBlueprintContainer.cs | 2 ++ .../Compose/Components/EditorSelectionHandler.cs | 4 ++-- .../Edit/Compose/Components/SelectionHandler.cs | 12 +++++++----- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 35896d4982..b47cf97a4d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -182,8 +182,7 @@ namespace osu.Game.Rulesets.Edit /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// - protected virtual ComposeBlueprintContainer CreateBlueprintContainer() - => new ComposeBlueprintContainer(this); + protected virtual ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(this); /// /// Construct a drawable ruleset for the provided ruleset. diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index bd22c7329a..7f81629f80 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Creates a which outlines s and handles movement of selections. /// - protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); + protected abstract SelectionHandler CreateSelectionHandler(); /// /// Creates a for a specific . diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index aef02d5ffd..0cb9cf930c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -135,6 +135,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override Container> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; + protected override SelectionHandler CreateSelectionHandler() => new EditorSelectionHandler(); + protected override void SelectAll() { Composer.Playfield.KeepAllAlive(); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 4c6833cc4a..4b85b5cc0e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -101,11 +101,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected virtual void UpdateTernaryStates() { - SelectionNewComboState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType(), h => h.NewCombo); + SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType(), h => h.NewCombo); foreach (var (sampleName, bindable) in SelectionSampleStates) { - bindable.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); + bindable.Value = GetStateFromSelection(SelectedItems, h => h.Samples.Any(s => s.Name == sampleName)); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 59ade746a9..4b99aa5da7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines s and handles movement of selections. /// - public class SelectionHandler : CompositeDrawable, IKeyBindingHandler + public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler { /// /// The currently selected blueprints. @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved(CanBeNull = true)] protected IEditorChangeHandler ChangeHandler { get; private set; } - public SelectionHandler() + protected SelectionHandler() { selectedBlueprints = new List>(); @@ -265,9 +265,11 @@ namespace osu.Game.Screens.Edit.Compose.Components DeleteSelected(); } - protected virtual void DeleteItems(IEnumerable items) - { - } + /// + /// Called whenever the deletion of items has been requested. + /// + /// The items to be deleted. + protected abstract void DeleteItems(IEnumerable items); /// /// Ensure the blueprint is in a selected state. From f97b14a20aeb786a03fe200e78a61376a5a23b56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:15:24 +0900 Subject: [PATCH 257/400] Fix binding direction of selected items --- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 4b85b5cc0e..d26a2946c1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -29,8 +29,8 @@ namespace osu.Game.Screens.Edit.Compose.Components // bring in updates from selection changes EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); - EditorBeatmap.SelectedHitObjects.BindTo(SelectedItems); + SelectedItems.BindTo(EditorBeatmap.SelectedHitObjects); SelectedItems.CollectionChanged += (sender, args) => { Scheduler.AddOnce(UpdateTernaryStates); From ff06a27a1237bc71febd6b4027449346c7c07ccd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:33:39 +0900 Subject: [PATCH 258/400] Revert changes to `OnBlueprint` methods and handle select-on-addition locally --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 2 +- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 7 ------- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 994e862946..f22404e77d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -251,7 +251,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; - protected override void OnBlueprintAdded(SelectionBlueprint item) + protected override void OnBlueprintAdded(HitObject item) { base.OnBlueprintAdded(item); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 0cb9cf930c..d17892d6ba 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -108,13 +108,6 @@ namespace osu.Game.Screens.Edit.Compose.Components base.AddBlueprintFor(item); } - protected override void OnBlueprintAdded(SelectionBlueprint blueprint) - { - base.OnBlueprintAdded(blueprint); - if (Beatmap.SelectedHitObjects.Contains(blueprint.Item)) - blueprint.Select(); - } - protected override void UpdateSelection() { base.UpdateSelection(); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 4b99aa5da7..52c148f852 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public IReadOnlyList> SelectedBlueprints => selectedBlueprints; - protected BindableList SelectedItems = new BindableList(); + public BindableList SelectedItems = new BindableList(); private readonly List> selectedBlueprints; From 7ec5ea1eb5a761760da2133dc03a4b9bce1882aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:33:47 +0900 Subject: [PATCH 259/400] Remove hitobject terminology from base classes --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 5 +- .../Compose/Components/BlueprintContainer.cs | 46 +++++++++++-------- .../Components/ComposeBlueprintContainer.cs | 4 +- .../Compose/Components/SelectionHandler.cs | 37 +++++++-------- .../Timeline/TimelineBlueprintContainer.cs | 4 +- 5 files changed, 52 insertions(+), 44 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 905e433731..b176ecb148 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -8,13 +8,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Edit { /// - /// A blueprint placed above a adding editing functionality. + /// A blueprint placed above a displaying item adding editing functionality. /// public abstract class SelectionBlueprint : CompositeDrawable, IStateful { @@ -86,7 +85,7 @@ namespace osu.Game.Rulesets.Edit protected virtual void OnDeselected() { - // selection blueprints are AlwaysPresent while the related DrawableHitObject is visible + // selection blueprints are AlwaysPresent while the related item is visible // set the body piece's alpha directly to avoid arbitrarily rendering frame buffers etc. of children. foreach (var d in InternalChildren) d.Hide(); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7f81629f80..c2ba627df4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -14,15 +14,13 @@ 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; using osuTK; using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A container which provides a "blueprint" display of hitobjects. + /// A container which provides a "blueprint" display of items. /// Includes selection and manipulation support via a . /// public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler @@ -65,15 +63,15 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual Container> CreateSelectionBlueprintContainer() => new Container> { RelativeSizeAxes = Axes.Both }; /// - /// Creates a which outlines s and handles movement of selections. + /// Creates a which outlines items and handles movement of selections. /// protected abstract SelectionHandler CreateSelectionHandler(); /// - /// Creates a for a specific . + /// Creates a for a specific item. /// - /// The to create the overlay for. - protected virtual SelectionBlueprint CreateBlueprintFor(T hitObject) => null; + /// The item to create the overlay for. + protected virtual SelectionBlueprint CreateBlueprintFor(T item) => null; protected virtual DragBox CreateDragBox(Action performSelect) => new DragBox(performSelect); @@ -103,7 +101,7 @@ namespace osu.Game.Screens.Edit.Compose.Components ClickedBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); // Deselection should only occur if no selected blueprints are hovered - // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection + // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the item and should not trigger deselection if (endClickSelection(e) || ClickedBlueprint != null) return true; @@ -237,7 +235,10 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionBlueprints.Add(blueprint); - OnBlueprintAdded(blueprint); + if (SelectionHandler.SelectedItems.Contains(item)) + blueprint.Select(); + + OnBlueprintAdded(blueprint.Item); } protected void RemoveBlueprintFor(T item) @@ -254,22 +255,24 @@ namespace osu.Game.Screens.Edit.Compose.Components if (movementBlueprints?.Contains(blueprint) == true) finishSelectionMovement(); - OnBlueprintRemoved(blueprint); + OnBlueprintRemoved(blueprint.Item); } /// - /// Called after a blueprint has been added. + /// Called after an item's blueprint has been added. /// - /// The for which the blueprint has been added. - protected virtual void OnBlueprintAdded(SelectionBlueprint blueprint) + /// The item for which the blueprint has been added. + protected virtual void OnBlueprintAdded(T item) { + if (SelectionHandler.SelectedItems.Contains(item)) + blueprintMap[item].Select(); } /// - /// Called after a blueprint has been removed. + /// Called after an item's blueprint has been removed. /// - /// The for which the blueprint has been removed. - protected virtual void OnBlueprintRemoved(SelectionBlueprint item) + /// The item for which the blueprint has been removed. + protected virtual void OnBlueprintRemoved(T item) { } @@ -398,16 +401,21 @@ namespace osu.Game.Screens.Edit.Compose.Components if (!SelectionHandler.SelectedBlueprints.Any()) return; - // Any selected blueprint that is hovered can begin the movement of the group, however only the earliest hitobject is used for movement + // Any selected blueprint that is hovered can begin the movement of the group, however only the first item (according to SortForMovement) is used for movement. // A special case is added for when a click selection occurred before the drag if (!clickSelectionBegan && !SelectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) return; - // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject + // Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); } + /// + /// Apply sorting of selected blueprints before performing movement. Generally used to surface the "main" item to the beginning of the collection. + /// + /// The blueprints to be moved. + /// Sorted blueprints. protected virtual IEnumerable> SortForMovement(IReadOnlyList> blueprints) => blueprints; /// @@ -442,7 +450,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } // if no positional snapping could be performed, try unrestricted snapping from the earliest - // hitobject in the selection. + // item in the selection. // The final movement position, relative to movementBlueprintOriginalPosition. Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index f22404e77d..f8ac0552ae 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -239,9 +239,9 @@ namespace osu.Game.Screens.Edit.Compose.Components updatePlacementPosition(); } - protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) + protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject item) { - var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); + var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == item); if (drawable == null) return null; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 52c148f852..8f2f1d65b8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -15,26 +15,27 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A component which outlines s and handles movement of selections. + /// A component which outlines items and handles movement of selections. /// public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler { /// /// The currently selected blueprints. /// Should be used when operations are dealing directly with the visible blueprints. - /// For more general selection operations, use instead. + /// For more general selection operations, use instead. /// public IReadOnlyList> SelectedBlueprints => selectedBlueprints; - public BindableList SelectedItems = new BindableList(); + /// + /// The currently selected items. + /// + public readonly BindableList SelectedItems = new BindableList(); private readonly List> selectedBlueprints; @@ -124,45 +125,45 @@ namespace osu.Game.Screens.Edit.Compose.Components #region User Input Handling /// - /// Handles the selected s being moved. + /// Handles the selected items being moved. /// /// - /// Just returning true is enough to allow updates to take place. + /// Just returning true is enough to allow default movement to take place. /// Custom implementation is only required if other attributes are to be considered, like changing columns. /// /// The move event. /// - /// Whether any s could be moved. + /// Whether any items could be moved. /// Returning true will also propagate StartTime changes provided by the closest . /// public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; /// - /// Handles the selected s being rotated. + /// Handles the selected items being rotated. /// /// The delta angle to apply to the selection. - /// Whether any s could be rotated. + /// Whether any items could be rotated. public virtual bool HandleRotation(float angle) => false; /// - /// Handles the selected s being scaled. + /// Handles the selected items being scaled. /// /// The delta scale to apply, in playfield local coordinates. /// The point of reference where the scale is originating from. - /// Whether any s could be scaled. + /// Whether any items could be scaled. public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; /// - /// Handles the selected s being flipped. + /// Handles the selected items being flipped. /// /// The direction to flip - /// Whether any s could be flipped. + /// Whether any items could be flipped. public virtual bool HandleFlip(Direction direction) => false; /// - /// Handles the selected s being reversed pattern-wise. + /// Handles the selected items being reversed pattern-wise. /// - /// Whether any s could be reversed. + /// Whether any items could be reversed. public virtual bool HandleReverse() => false; public bool OnPressed(PlatformAction action) @@ -196,7 +197,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. internal virtual void HandleSelected(SelectionBlueprint blueprint) { - // there are potentially multiple SelectionHandlers active, but we only want to add hitobjects to the selected list once. + // there are potentially multiple SelectionHandlers active, but we only want to add items to the selected list once. if (!SelectedItems.Contains(blueprint.Item)) SelectedItems.Add(blueprint.Item); @@ -323,7 +324,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (selectedBlueprints.Count == 0) return; - // Move the rectangle to cover the hitobjects + // Move the rectangle to cover the items var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 2bb80bc27b..7c1bbd65f9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -179,9 +179,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); - protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) + protected override SelectionBlueprint CreateBlueprintFor(HitObject item) { - return new TimelineHitObjectBlueprint(hitObject) + return new TimelineHitObjectBlueprint(item) { OnDragHandled = handleScrollViaDrag }; From 42255f8d3350673f085835da8eca68a6a3df382c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:57:51 +0900 Subject: [PATCH 260/400] Rename and xmldoc selection completed method --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 7 +++++-- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index c2ba627df4..392b375d12 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (isDraggingBlueprint) { - UpdateSelection(); + DragOperationCompleted(); changeHandler?.EndChange(); } @@ -182,7 +182,10 @@ namespace osu.Game.Screens.Edit.Compose.Components DragBox.Hide(); } - protected virtual void UpdateSelection() + /// + /// Called whenever a drag operation completes, before any change transaction is committed. + /// + protected virtual void DragOperationCompleted() { } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index d17892d6ba..9eee680bda 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -108,9 +108,9 @@ namespace osu.Game.Screens.Edit.Compose.Components base.AddBlueprintFor(item); } - protected override void UpdateSelection() + protected override void DragOperationCompleted() { - base.UpdateSelection(); + base.DragOperationCompleted(); // handle positional change etc. foreach (var blueprint in SelectionBlueprints) From b87446a5778ee76c96f02cd163beebdc44476e24 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 19:37:01 +0900 Subject: [PATCH 261/400] Simplify HitObjectLifetimeEntry logic a bit --- .../Objects/HitObjectLifetimeEntry.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 0181d6241b..0d1eb68f07 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -45,23 +45,16 @@ namespace osu.Game.Rulesets.Objects // This method is called even if `start == LifetimeStart` when `KeepAlive` is true (necessary to update `realLifetimeStart`). protected override void SetLifetimeStart(double start) { - // This assignment cannot be done in `SetLifetime` because otherwise setting only `LifetimeStart` will make `realLifetimeEnd` to be lost. realLifetimeStart = start; - base.SetLifetimeStart(start); + if (!keepAlive) + base.SetLifetimeStart(start); } protected override void SetLifetimeEnd(double end) { realLifetimeEnd = end; - base.SetLifetimeEnd(end); - } - - protected override void SetLifetime(double start, double end) - { - if (keepAlive) - base.SetLifetime(double.MinValue, double.MaxValue); - else - base.SetLifetime(start, end); + if (!keepAlive) + base.SetLifetimeEnd(end); } private bool keepAlive; @@ -77,7 +70,10 @@ namespace osu.Game.Rulesets.Objects return; keepAlive = value; - SetLifetime(realLifetimeStart, realLifetimeEnd); + if (keepAlive) + SetLifetime(double.MinValue, double.MaxValue); + else + SetLifetime(realLifetimeStart, realLifetimeEnd); } } From c4d28110d641c9d121d88e4559c783cfc2094218 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Tue, 27 Apr 2021 19:02:57 +0800 Subject: [PATCH 262/400] Add visual tests for timing based note coloring --- .../TestSceneTimingBasedNoteColouring.cs | 88 +++++++++++++++++++ .../Objects/Drawables/DrawableNote.cs | 2 +- 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs new file mode 100644 index 0000000000..3a9ddb6a9d --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -0,0 +1,88 @@ +// 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.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Tests.Visual; +using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Configuration; +using osu.Framework.Bindables; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class TestSceneTimingBasedNoteColouring : OsuTestScene + { + [Resolved] + private RulesetConfigCache configCache { get; set; } + private readonly Bindable configTimingBasedNoteColouring = new Bindable(); + + protected override void LoadComplete() + { + const int beatLength = 500; + + var ruleset = new ManiaRuleset(); + + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }) + { + HitObjects = { + new Note { StartTime = 0 }, + new Note { StartTime = beatLength / 16 }, + new Note { StartTime = beatLength / 12 }, + new Note { StartTime = beatLength / 8 }, + new Note { StartTime = beatLength / 6 }, + new Note { StartTime = beatLength / 4 }, + new Note { StartTime = beatLength / 3 }, + new Note { StartTime = beatLength / 2 }, + new Note { StartTime = beatLength } + }, + ControlPointInfo = new ControlPointInfo(), + BeatmapInfo = + { + BaseDifficulty = new BeatmapDifficulty + { + SliderTickRate = 4, + OverallDifficulty = 10, + }, + Ruleset = ruleset.RulesetInfo + }, + }; + + foreach (var note in beatmap.HitObjects) + { + note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + } + + beatmap.ControlPointInfo.Add(0, new TimingControlPoint() + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = beatLength + } + ); + + Child = new Container + { + Clock = new FramedClock(new ManualClock()), + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new[] + { + ruleset.CreateDrawableRulesetWith(beatmap) + } + }; + + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring); + + AddStep("Enable", () => configTimingBasedNoteColouring.Value = true); + AddStep("Disable", () => configTimingBasedNoteColouring.Value = false); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 102cd485dc..942a32936c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved] private OsuColour colours { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private BeatDivisorFinder beatDivisorFinder { get; set; } private readonly Bindable configTimingBasedNoteColouring = new Bindable(); From 4752a0201ad2373c2bfefb9ae8328e5de8609298 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Tue, 27 Apr 2021 19:25:46 +0800 Subject: [PATCH 263/400] Fix cake errors --- .../TestSceneTimingBasedNoteColouring.cs | 26 +++--- osu.Game.Tests/NonVisual/BeatDivisorFinder.cs | 90 +++++++++---------- 2 files changed, 59 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs index 3a9ddb6a9d..5cfd7ff389 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -21,26 +21,28 @@ namespace osu.Game.Rulesets.Mania.Tests { [Resolved] private RulesetConfigCache configCache { get; set; } + private readonly Bindable configTimingBasedNoteColouring = new Bindable(); protected override void LoadComplete() { - const int beatLength = 500; + const double beat_length = 500; var ruleset = new ManiaRuleset(); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }) { - HitObjects = { + HitObjects = + { new Note { StartTime = 0 }, - new Note { StartTime = beatLength / 16 }, - new Note { StartTime = beatLength / 12 }, - new Note { StartTime = beatLength / 8 }, - new Note { StartTime = beatLength / 6 }, - new Note { StartTime = beatLength / 4 }, - new Note { StartTime = beatLength / 3 }, - new Note { StartTime = beatLength / 2 }, - new Note { StartTime = beatLength } + new Note { StartTime = beat_length / 16 }, + new Note { StartTime = beat_length / 12 }, + new Note { StartTime = beat_length / 8 }, + new Note { StartTime = beat_length / 6 }, + new Note { StartTime = beat_length / 4 }, + new Note { StartTime = beat_length / 3 }, + new Note { StartTime = beat_length / 2 }, + new Note { StartTime = beat_length } }, ControlPointInfo = new ControlPointInfo(), BeatmapInfo = @@ -59,10 +61,10 @@ namespace osu.Game.Rulesets.Mania.Tests note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } - beatmap.ControlPointInfo.Add(0, new TimingControlPoint() + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = beatLength + BeatLength = beat_length } ); diff --git a/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs b/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs index 742f5790d6..720d8e8ecd 100644 --- a/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs +++ b/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs @@ -14,32 +14,32 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestFindDivisor() { - const int beatLength = 1000; + const double beat_length = 1000; var beatmap = new Beatmap { HitObjects = new List { - new HitObject { StartTime = -beatLength / 3 }, + new HitObject { StartTime = -beat_length / 3 }, new HitObject { StartTime = 0 }, - new HitObject { StartTime = beatLength / 16 }, - new HitObject { StartTime = beatLength / 12 }, - new HitObject { StartTime = beatLength / 8 }, - new HitObject { StartTime = beatLength / 6 }, - new HitObject { StartTime = beatLength / 4 }, - new HitObject { StartTime = beatLength / 3 }, - new HitObject { StartTime = beatLength / 2 }, - new HitObject { StartTime = beatLength }, - new HitObject { StartTime = beatLength + beatLength / 7 } + new HitObject { StartTime = beat_length / 16 }, + new HitObject { StartTime = beat_length / 12 }, + new HitObject { StartTime = beat_length / 8 }, + new HitObject { StartTime = beat_length / 6 }, + new HitObject { StartTime = beat_length / 4 }, + new HitObject { StartTime = beat_length / 3 }, + new HitObject { StartTime = beat_length / 2 }, + new HitObject { StartTime = beat_length }, + new HitObject { StartTime = beat_length + beat_length / 7 } }, ControlPointInfo = new ControlPointInfo() }; - beatmap.ControlPointInfo.Add(0, new TimingControlPoint() - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = beatLength - }); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = beat_length + }); var beatDivisorFinder = new BeatDivisorFinder(beatmap); @@ -59,49 +59,49 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestFindDivisorWithTempoChanges() { - const int firstBeatLength = 1000; - const int secondBeatLength = 700; - const int thirdBeatLength = 200; + const double first_beat_length = 1000; + const double second_beat_length = 700; + const double third_beat_length = 200; - const int firstBeatLengthStart = 0; - const int secondBeatLengthStart = 1000; - const int thirdBeatLengthStart = 2000; + const double first_beat_length_start = 0; + const double second_beat_length_start = 1000; + const double third_beat_length_start = 2000; var beatmap = new Beatmap { HitObjects = new List { - new HitObject { StartTime = firstBeatLengthStart }, - new HitObject { StartTime = firstBeatLengthStart + firstBeatLength / 2 }, - new HitObject { StartTime = secondBeatLengthStart }, - new HitObject { StartTime = secondBeatLengthStart + secondBeatLength / 2 }, - new HitObject { StartTime = thirdBeatLengthStart }, - new HitObject { StartTime = thirdBeatLengthStart + thirdBeatLength / 2 }, + new HitObject { StartTime = first_beat_length_start }, + new HitObject { StartTime = first_beat_length_start + first_beat_length / 2 }, + new HitObject { StartTime = second_beat_length_start }, + new HitObject { StartTime = second_beat_length_start + second_beat_length / 2 }, + new HitObject { StartTime = third_beat_length_start }, + new HitObject { StartTime = third_beat_length_start + third_beat_length / 2 }, }, ControlPointInfo = new ControlPointInfo() }; - var firstTimingControlPoint = new TimingControlPoint() + var firstTimingControlPoint = new TimingControlPoint { TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = firstBeatLength - }; - - var secondTimingControlPoint = new TimingControlPoint() - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = secondBeatLength - }; - - var thirdTimingControlPoint = new TimingControlPoint() - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = thirdBeatLength + BeatLength = first_beat_length }; - beatmap.ControlPointInfo.Add(firstBeatLengthStart, firstTimingControlPoint); - beatmap.ControlPointInfo.Add(secondBeatLengthStart, secondTimingControlPoint); - beatmap.ControlPointInfo.Add(thirdBeatLengthStart, thirdTimingControlPoint); + var secondTimingControlPoint = new TimingControlPoint + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = second_beat_length + }; + + var thirdTimingControlPoint = new TimingControlPoint + { + TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, + BeatLength = third_beat_length + }; + + beatmap.ControlPointInfo.Add(first_beat_length_start, firstTimingControlPoint); + beatmap.ControlPointInfo.Add(second_beat_length_start, secondTimingControlPoint); + beatmap.ControlPointInfo.Add(third_beat_length_start, thirdTimingControlPoint); var beatDivisorFinder = new BeatDivisorFinder(beatmap); From 200352b7507cb234f6fce5f287e06492caf87606 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 13:56:05 +0200 Subject: [PATCH 264/400] Rename unsnap check templates --- .../Editing/Checks/CheckUnsnappedObjectsTest.cs | 8 ++++---- .../Edit/Checks/CheckUnsnappedObjects.cs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs index f8cac331bc..5e65b263f2 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs @@ -108,8 +108,8 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(2)); - Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate1MsOrMore)); - Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate2MsOrMore)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap)); + Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateLargeUnsnap)); } private Mock getSliderMock(double startTime, double endTime, int repeats = 0) @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); - Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate1MsOrMore)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap)); } private void assert2Ms(List hitobjects, int count = 1) @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); - Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplate2MsOrMore)); + Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateLargeUnsnap)); } private IBeatmap getPlayableBeatmap(List hitobjects) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index 74a2ce2fd7..8b6bb7d461 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable PossibleTemplates => new IssueTemplate[] { - new IssueTemplate2MsOrMore(this), - new IssueTemplate1MsOrMore(this) + new IssueTemplateLargeUnsnap(this), + new IssueTemplateSmallUnsnap(this) }; public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) @@ -55,9 +55,9 @@ namespace osu.Game.Rulesets.Edit.Checks private IEnumerable getUnsnapIssues(HitObject hitobject, double unsnap, double time, string postfix = "") { if (Math.Abs(unsnap) >= UNSNAP_MS_THRESHOLD) - yield return new IssueTemplate2MsOrMore(this).Create(hitobject, unsnap, time, postfix); + yield return new IssueTemplateLargeUnsnap(this).Create(hitobject, unsnap, time, postfix); else if (Math.Abs(unsnap) >= 1) - yield return new IssueTemplate1MsOrMore(this).Create(hitobject, unsnap, time, postfix); + yield return new IssueTemplateSmallUnsnap(this).Create(hitobject, unsnap, time, postfix); // We don't care about unsnaps < 1 ms, as all object ends have these due to the way SV works. } @@ -79,17 +79,17 @@ namespace osu.Game.Rulesets.Edit.Checks } } - public class IssueTemplate2MsOrMore : IssueTemplateUnsnap + public class IssueTemplateLargeUnsnap : IssueTemplateUnsnap { - public IssueTemplate2MsOrMore(ICheck check) + public IssueTemplateLargeUnsnap(ICheck check) : base(check, IssueType.Problem) { } } - public class IssueTemplate1MsOrMore : IssueTemplateUnsnap + public class IssueTemplateSmallUnsnap : IssueTemplateUnsnap { - public IssueTemplate1MsOrMore(ICheck check) + public IssueTemplateSmallUnsnap(ICheck check) : base(check, IssueType.Negligible) { } From 5dadfd04e7c11f0e69982401fe5647ba67621067 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 22:36:25 +0900 Subject: [PATCH 265/400] 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 e0b07549f4..5aee9e15cc 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 9a43d5f031..986bd8e7ba 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2c41b3ef26..c32109d6db 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 3b04aed491abef2278c34f497ead995a839c6f52 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Apr 2021 22:31:23 +0900 Subject: [PATCH 266/400] Add failing test --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 20fa0732b9..2c862fe8a8 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -287,6 +287,23 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.AreEqual(expectedReturnValue, hitResult.IsScorable()); } + [TestCase(HitResult.Perfect, 1_000_000)] + [TestCase(HitResult.SmallTickHit, 1_000_000)] + [TestCase(HitResult.LargeTickHit, 1_000_000)] + [TestCase(HitResult.SmallBonus, 700_000 + Judgement.SMALL_BONUS_SCORE)] + [TestCase(HitResult.LargeBonus, 700_000 + Judgement.LARGE_BONUS_SCORE)] + public void TestGetScoreWithExternalStatistics(HitResult result, int expectedScore) + { + var statistic = new Dictionary { { result, 1 } }; + + scoreProcessor.ApplyBeatmap(new Beatmap + { + HitObjects = { new TestHitObject(result) } + }); + + Assert.That(scoreProcessor.GetImmediateScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } From 1281993f1f9b106c6e77c05cf59b96e85590feda Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Apr 2021 22:35:31 +0900 Subject: [PATCH 267/400] Fix bonus score not calculated from the correct statistics --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 0fb5c2f4b5..a16a6a6c9b 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -252,7 +252,7 @@ namespace osu.Game.Rulesets.Scoring computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; } - return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), scoreResultCounts); + return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); } /// From baa6e845aa812d91a15c655b459de4fbaa203c09 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Apr 2021 22:38:37 +0900 Subject: [PATCH 268/400] Change to fluent assertions --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 2c862fe8a8..ac26f4723e 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; @@ -51,7 +50,7 @@ namespace osu.Game.Tests.Rulesets.Scoring }; scoreProcessor.ApplyResult(judgementResult); - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d)); } /// @@ -118,7 +117,7 @@ namespace osu.Game.Tests.Rulesets.Scoring scoreProcessor.ApplyResult(judgementResult); } - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d)); } /// @@ -158,7 +157,7 @@ namespace osu.Game.Tests.Rulesets.Scoring }; scoreProcessor.ApplyResult(lastJudgementResult); - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d)); } [Test] @@ -169,7 +168,7 @@ namespace osu.Game.Tests.Rulesets.Scoring scoreProcessor.Mode.Value = scoringMode; scoreProcessor.ApplyBeatmap(new TestBeatmap(new RulesetInfo())); - Assert.IsTrue(Precision.AlmostEquals(0, scoreProcessor.TotalScore.Value)); + Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); } [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] From b8b6d0e861bd0269b841ceacfaab3c361128bc3b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 27 Apr 2021 16:54:47 +0200 Subject: [PATCH 269/400] Add tests for `ClosestBeatDivisor` Used https://github.com/ppy/osu/pull/12558/files#diff-5c1f04c5b262ca3abbaf867aa91b62a60b66691323c286ad5aa0b75c153cc6ca as reference. --- .../NonVisual/ClosestBeatDivisorTest.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs diff --git a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs new file mode 100644 index 0000000000..4d6986f5d2 --- /dev/null +++ b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs @@ -0,0 +1,91 @@ +// 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.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Tests.NonVisual +{ + public class ClosestBeatDivisorTest + { + [Test] + public void TestExactDivisors() + { + var cpi = new ControlPointInfo(); + cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 }); + + double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 }; + + assertClosestDivisors(divisors, divisors, cpi); + } + + [Test] + public void TestExactDivisorWithTempoChanges() + { + int offset = 0; + int[] beatLengths = { 1000, 200, 100, 50 }; + + var cpi = new ControlPointInfo(); + + foreach (int beatLength in beatLengths) + { + cpi.Add(offset, new TimingControlPoint { BeatLength = beatLength }); + offset += beatLength * 2; + } + + double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3 }; + + assertClosestDivisors(divisors, divisors, cpi); + } + + [Test] + public void TestExactDivisorsHighBPMStream() + { + var cpi = new ControlPointInfo(); + cpi.Add(-50, new TimingControlPoint { BeatLength = 50 }); // 1200 BPM 1/4 (limit testing) + + // A 1/4 stream should land on 1/1, 1/2 and 1/4 divisors. + double[] divisors = { 4, 4, 4, 4, 4, 4, 4, 4 }; + double[] closestDivisors = { 4, 2, 4, 1, 4, 2, 4, 1 }; + + assertClosestDivisors(divisors, closestDivisors, cpi, step: 1 / 4d); + } + + [Test] + public void TestApproximateDivisors() + { + var cpi = new ControlPointInfo(); + cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 }); + + double[] divisors = { 3.03d, 0.97d, 14, 13, 7.94d, 6.08d, 3.93d, 2.96d, 2.02d, 64 }; + double[] closestDivisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 }; + + assertClosestDivisors(divisors, closestDivisors, cpi); + } + + private void assertClosestDivisors(IReadOnlyList divisors, IReadOnlyList closestDivisors, ControlPointInfo cpi, double step = 1) + { + List hitobjects = new List(); + double offset = cpi.TimingPoints[0].Time; + + for (int i = 0; i < divisors.Count; ++i) + { + double beatLength = cpi.TimingPointAt(offset).BeatLength; + hitobjects.Add(new HitObject { StartTime = offset + beatLength / divisors[i] }); + offset += beatLength * step; + } + + var beatmap = new Beatmap + { + HitObjects = hitobjects, + ControlPointInfo = cpi + }; + + for (int i = 0; i < divisors.Count; ++i) + Assert.AreEqual(closestDivisors[i], beatmap.ClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}"); + } + } +} From 4e3ee773968e52788ccf73bca74fe883d72da113 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 02:46:34 +0900 Subject: [PATCH 270/400] Add support for custom controls to SettingSourceAttribute --- .../Mods/SettingsSourceAttributeTest.cs | 29 +++++++++++++++++++ .../Configuration/SettingSourceAttribute.cs | 29 ++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs index 883c9d1ac2..dd105787fa 100644 --- a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs +++ b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs @@ -4,7 +4,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; +using osu.Game.Overlays.Settings; namespace osu.Game.Tests.Mods { @@ -26,6 +29,16 @@ namespace osu.Game.Tests.Mods Assert.That(orderedSettings[3].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.UnorderedSetting))); } + [Test] + public void TestCustomControl() + { + var objectWithCustomSettingControl = new ClassWithCustomSettingControl(); + var settings = objectWithCustomSettingControl.CreateSettingsControls().ToArray(); + + Assert.That(settings, Has.Length.EqualTo(1)); + Assert.That(settings[0], Is.TypeOf()); + } + private class ClassWithSettings { [SettingSource("Unordered setting", "Should be last")] @@ -40,5 +53,21 @@ namespace osu.Game.Tests.Mods [SettingSource("Third setting", "Yet another description", 3)] public BindableInt ThirdSetting { get; set; } = new BindableInt(); } + + private class ClassWithCustomSettingControl + { + [SettingSource("Custom setting", "Should be a custom control", SettingControlType = typeof(CustomSettingsControl))] + public BindableInt UnorderedSetting { get; set; } = new BindableInt(); + } + + private class CustomSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new CustomControl(); + + private class CustomControl : Drawable, IHasCurrentValue + { + public Bindable Current { get; set; } = new Bindable(); + } + } } } diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index cfce615130..13cf0a9f0c 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.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. +#nullable enable + using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using JetBrains.Annotations; using osu.Framework.Bindables; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Overlays.Settings; @@ -31,7 +34,15 @@ namespace osu.Game.Configuration public int? OrderPosition { get; } - public SettingSourceAttribute(string label, string description = null) + /// + /// The type of the settings control which handles this setting source. + /// + /// + /// Must be a type deriving with a public constructor. + /// + public Type? SettingControlType { get; set; } + + public SettingSourceAttribute(string? label, string? description = null) { Label = label ?? string.Empty; Description = description ?? string.Empty; @@ -67,6 +78,22 @@ namespace osu.Game.Configuration { object value = property.GetValue(obj); + if (attr.SettingControlType != null) + { + var controlType = attr.SettingControlType; + if (controlType.EnumerateBaseTypes().All(t => !t.IsGenericType || t.GetGenericTypeDefinition() != typeof(SettingsItem<>))) + throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} had an unsupported custom control type ({controlType.ReadableName()})"); + + var control = (Drawable)Activator.CreateInstance(controlType); + controlType.GetProperty(nameof(SettingsItem.LabelText))?.SetValue(control, attr.Label); + controlType.GetProperty(nameof(SettingsItem.TooltipText))?.SetValue(control, attr.Description); + controlType.GetProperty(nameof(SettingsItem.Current))?.SetValue(control, value); + + yield return control; + + continue; + } + switch (value) { case BindableNumber bNumber: From 61b7dc1e0601feea555c2841249714a9bc684f68 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 03:42:29 +0900 Subject: [PATCH 271/400] Fix bonus-only maps having 700K base score --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 12 ++++++------ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index ac26f4723e..baa10663d5 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -83,8 +83,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] // (3 * 10) / (4 * 10) * 300_000 + 700_000 (max combo 0) [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (3 * 0) / (4 * 30) * 300_000 + (0 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] // (3 * 30) / (4 * 30) * 300_000 + (0 / 4) * 700_000 - [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 700_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 700_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) @@ -95,8 +95,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] // (0 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -289,8 +289,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.Perfect, 1_000_000)] [TestCase(HitResult.SmallTickHit, 1_000_000)] [TestCase(HitResult.LargeTickHit, 1_000_000)] - [TestCase(HitResult.SmallBonus, 700_000 + Judgement.SMALL_BONUS_SCORE)] - [TestCase(HitResult.LargeBonus, 700_000 + Judgement.LARGE_BONUS_SCORE)] + [TestCase(HitResult.SmallBonus, 1_000_000 + Judgement.SMALL_BONUS_SCORE)] + [TestCase(HitResult.LargeBonus, 1_000_000 + Judgement.LARGE_BONUS_SCORE)] public void TestGetScoreWithExternalStatistics(HitResult result, int expectedScore) { var statistic = new Dictionary { { result, 1 } }; diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index a16a6a6c9b..f32f70d4ba 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -266,7 +266,7 @@ namespace osu.Game.Rulesets.Scoring if (preferRolling && rollingMaxBaseScore != 0) return baseScore / rollingMaxBaseScore; - return maxBaseScore > 0 ? baseScore / maxBaseScore : 0; + return maxBaseScore > 0 ? baseScore / maxBaseScore : 1; } private double calculateComboRatio(int maxCombo) => maxAchievableCombo > 0 ? (double)maxCombo / maxAchievableCombo : 1; From 04454062b7dad5cd47b6bd066c23a018e5290f06 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 03:52:59 +0900 Subject: [PATCH 272/400] Fix up xmldoc --- osu.Game/Configuration/SettingSourceAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 13cf0a9f0c..a9436aac89 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -38,7 +38,7 @@ namespace osu.Game.Configuration /// The type of the settings control which handles this setting source. /// /// - /// Must be a type deriving with a public constructor. + /// Must be a type deriving . /// public Type? SettingControlType { get; set; } From 05bd6ee50cc20a014c958c86752152ca80e3e553 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 03:54:42 +0900 Subject: [PATCH 273/400] Add back ctor doc --- osu.Game/Configuration/SettingSourceAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index a9436aac89..3e50613093 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -38,7 +38,7 @@ namespace osu.Game.Configuration /// The type of the settings control which handles this setting source. /// /// - /// Must be a type deriving . + /// Must be a type deriving with a public parameterless constructor. /// public Type? SettingControlType { get; set; } From aa1cb65eaad96ebdc07d1a5e626284b91957af33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:42:10 +0900 Subject: [PATCH 274/400] Rename region to be more inclusive --- .../Components/EditorSelectionHandler.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index d26a2946c1..bb7abd8d8f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #endregion - #region Sample Changes + #region Ternary state changes /// /// Adds a hit sample to all selected s. @@ -140,6 +140,15 @@ namespace osu.Game.Screens.Edit.Compose.Components }); } + /// + /// Removes a hit sample from all selected s. + /// + /// The name of the hit sample. + public void RemoveHitSample(string sampleName) + { + EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName)); + } + /// /// Set the new combo state of all selected s. /// @@ -158,15 +167,6 @@ namespace osu.Game.Screens.Edit.Compose.Components }); } - /// - /// Removes a hit sample from all selected s. - /// - /// The name of the hit sample. - public void RemoveHitSample(string sampleName) - { - EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName)); - } - #endregion #region Context Menu From 61d4eb177710e44f3e17586f01da6f23048b360c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:44:19 +0900 Subject: [PATCH 275/400] Remove unnecessary and out-of-place xmldoc --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 8f2f1d65b8..0a1f313407 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -134,7 +134,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The move event. /// /// Whether any items could be moved. - /// Returning true will also propagate StartTime changes provided by the closest . /// public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; From bc455005a5ef1dcd739518ec4e1dec86f3838457 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:44:50 +0900 Subject: [PATCH 276/400] Fix incorrect coordinate space mention in xmldoc --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 0a1f313407..a19798ed9a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Handles the selected items being scaled. /// - /// The delta scale to apply, in playfield local coordinates. + /// The delta scale to apply, in local coordinates. /// The point of reference where the scale is originating from. /// Whether any items could be scaled. public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; From d0be8f9fb31e6794e68523f2be42c8cfe70223c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:45:36 +0900 Subject: [PATCH 277/400] Remove one more out-of-date comment --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 392b375d12..7c1b488163 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -354,7 +354,6 @@ namespace osu.Game.Screens.Edit.Compose.Components break; case SelectionState.Selected: - // if the editor is playing, we generally don't want to deselect objects even if outside the selection area. if (AllowDeselection && !isValidForSelection()) blueprint.Deselect(); break; From a9a5809e940f2f424958533d6f6e46e133b985f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:46:52 +0900 Subject: [PATCH 278/400] Fix incorrect xmldoc in `MoveSelectionEvent` --- .../Screens/Edit/Compose/Components/MoveSelectionEvent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index a07b434011..a32c30b579 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -17,12 +17,12 @@ namespace osu.Game.Screens.Edit.Compose.Components public readonly SelectionBlueprint Blueprint; /// - /// The expected screen-space position of the hitobject at the current cursor position. + /// The expected screen-space position of the blueprint's item at the current cursor position. /// public readonly Vector2 ScreenSpacePosition; /// - /// The distance between and the hitobject's current position, in the coordinate-space of the hitobject's parent. + /// The distance between and the blueprint's current position, in the coordinate-space of the blueprint item's parent. /// public readonly Vector2 InstantDelta; From e4f2e0131c94afbfb95f8b680dfb8c8d37144a8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 12:02:55 +0900 Subject: [PATCH 279/400] Rename `AllowDeselection` to better match use case --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 6 +++--- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7c1b488163..7f31afb9ba 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -76,9 +76,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual DragBox CreateDragBox(Action performSelect) => new DragBox(performSelect); /// - /// Whether this component is in a state where deselection should be allowed. If false, selection will only be added to. + /// Whether this component is in a state where items outside a drag selection should be deselected. If false, selection will only be added to. /// - protected virtual bool AllowDeselection => true; + protected virtual bool AllowDeselectionDuringDrag => true; protected override bool OnMouseDown(MouseDownEvent e) { @@ -354,7 +354,7 @@ namespace osu.Game.Screens.Edit.Compose.Components break; case SelectionState.Selected: - if (AllowDeselection && !isValidForSelection()) + if (AllowDeselectionDuringDrag && !isValidForSelection()) blueprint.Deselect(); break; } diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 9eee680bda..2a605f75d8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override IEnumerable> SortForMovement(IReadOnlyList> blueprints) => blueprints.OrderBy(b => b.Item.StartTime); - protected override bool AllowDeselection => !EditorClock.IsRunning; + protected override bool AllowDeselectionDuringDrag => !EditorClock.IsRunning; protected override bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) { From 43772f4303aedcec12b075899b09873db3f766f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 12:03:41 +0900 Subject: [PATCH 280/400] Remove duplicated call to initially select blueprint --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7f31afb9ba..a7076bf40e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -267,8 +267,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The item for which the blueprint has been added. protected virtual void OnBlueprintAdded(T item) { - if (SelectionHandler.SelectedItems.Contains(item)) - blueprintMap[item].Select(); } /// From 532ec403951c0efdce9abdd4c29c085133079baf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 12:04:48 +0900 Subject: [PATCH 281/400] Remove unnecessary newline --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index a7076bf40e..1f9cd0258e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -174,7 +174,6 @@ namespace osu.Game.Screens.Edit.Compose.Components if (isDraggingBlueprint) { DragOperationCompleted(); - changeHandler?.EndChange(); } From 4c9e94da2b05b5c5679848258223767b7927b8a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 13:43:16 +0900 Subject: [PATCH 282/400] Move context menu logic to base class --- .../Edit/TaikoSelectionHandler.cs | 3 ++ .../Components/EditorSelectionHandler.cs | 51 +++++-------------- .../Compose/Components/SelectionHandler.cs | 37 +++++++++++++- 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 20366e5b3a..48ee0d4cf4 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -80,6 +80,9 @@ namespace osu.Game.Rulesets.Taiko.Edit if (selection.All(s => s.Item is TaikoHitObject)) yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; + + foreach (var item in base.GetContextMenuItemsForSelection(selection)) + yield return item; } public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index bb7abd8d8f..0fc305dcc4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -7,7 +7,6 @@ using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Game.Audio; using osu.Game.Graphics.UserInterface; @@ -17,7 +16,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Screens.Edit.Compose.Components { - public class EditorSelectionHandler : SelectionHandler, IHasContextMenu + public class EditorSelectionHandler : SelectionHandler { [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } @@ -171,46 +170,24 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Context Menu - public MenuItem[] ContextMenuItems - { - get - { - if (!SelectedBlueprints.Any(b => b.IsHovered)) - return Array.Empty(); - - var items = new List(); - - items.AddRange(GetContextMenuItemsForSelection(SelectedBlueprints)); - - if (SelectedBlueprints.All(b => b.Item is IHasComboInformation)) - { - items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }); - } - - if (SelectedBlueprints.Count == 1) - items.AddRange(SelectedBlueprints[0].ContextMenuItems); - - items.AddRange(new[] - { - new OsuMenuItem("Sound") - { - Items = SelectionSampleStates.Select(kvp => - new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() - }, - new OsuMenuItem("Delete", MenuItemType.Destructive, DeleteSelected), - }); - - 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(); + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) + { + if (SelectedBlueprints.All(b => b.Item is IHasComboInformation)) + { + yield return new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }; + } + + yield return new OsuMenuItem("Sound") + { + Items = SelectionSampleStates.Select(kvp => + new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() + }; + } #endregion } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index a19798ed9a..02df004129 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -8,12 +8,15 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osuTK; using osuTK.Input; @@ -23,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines items and handles movement of selections. /// - public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler + public abstract class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { /// /// The currently selected blueprints. @@ -341,5 +344,37 @@ namespace osu.Game.Screens.Edit.Compose.Components } #endregion + + #region Context Menu + + public MenuItem[] ContextMenuItems + { + get + { + if (!SelectedBlueprints.Any(b => b.IsHovered)) + return Array.Empty(); + + var items = new List(); + + items.AddRange(GetContextMenuItemsForSelection(SelectedBlueprints)); + + if (SelectedBlueprints.Count == 1) + items.AddRange(SelectedBlueprints[0].ContextMenuItems); + + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, DeleteSelected)); + + 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(); + + #endregion } } From e0906daebf9f17dfca21fa87f776df29e9d568bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 13:49:41 +0900 Subject: [PATCH 283/400] Change one remaining instance of incorrect terminology in xmldoc --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 02df004129..cb3424a250 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -312,7 +312,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } /// - /// Triggered whenever the set of selected objects changes. + /// Triggered whenever the set of selected items changes. /// Should update the selection box's state to match supported operations. /// protected virtual void OnSelectionChanged() From ac1534cda2fb51afad43bcc4e4f4fe945a232cde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 15:54:40 +0900 Subject: [PATCH 284/400] Add test covering existing button actually changing to `LocallyAvailable` state --- osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index 0c199bfb62..3fc894da0d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -43,7 +43,10 @@ namespace osu.Game.Tests.Visual.Online createButtonWithBeatmap(createSoleily()); AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); + AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); + AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); + createButtonWithBeatmap(createSoleily()); AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); ensureSoleilyRemoved(); From 05e3a73a7d24acf00431405b96234498f830ddcb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 15:54:58 +0900 Subject: [PATCH 285/400] Fix import cancellation not correctly being forwarded to import notification --- osu.Game/Database/ArchiveModelManager.cs | 49 +++++++++++++++--------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 6719351530..dbeaebb1cd 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -156,33 +156,44 @@ namespace osu.Game.Database bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; - await Task.WhenAll(tasks.Select(async task => + try { - notification.CancellationToken.ThrowIfCancellationRequested(); - - try + await Task.WhenAll(tasks.Select(async task => { - var model = await Import(task, isLowPriorityImport, notification.CancellationToken).ConfigureAwait(false); + notification.CancellationToken.ThrowIfCancellationRequested(); - lock (imported) + try { - if (model != null) - imported.Add(model); - current++; + var model = await Import(task, isLowPriorityImport, notification.CancellationToken).ConfigureAwait(false); - notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; - notification.Progress = (float)current / tasks.Length; + lock (imported) + { + if (model != null) + imported.Add(model); + current++; + + notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; + notification.Progress = (float)current / tasks.Length; + } } - } - catch (TaskCanceledException) + catch (TaskCanceledException) + { + throw; + } + catch (Exception e) + { + Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); + } + })).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + if (imported.Count == 0) { - throw; + notification.State = ProgressNotificationState.Cancelled; + return imported; } - catch (Exception e) - { - Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); - } - })).ConfigureAwait(false); + } if (imported.Count == 0) { From 8598a0968f17c0733ec38e030abceeddc8dca37a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:29:23 +0900 Subject: [PATCH 286/400] Update calculations in comments to match new logic Mostly look to be errors that existed before this PR. Co-authored-by: Endrik --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index baa10663d5..41e16ebeaf 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -83,8 +83,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] // (3 * 10) / (4 * 10) * 300_000 + 700_000 (max combo 0) [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (3 * 0) / (4 * 30) * 300_000 + (0 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] // (3 * 30) / (4 * 30) * 300_000 + (0 / 4) * 700_000 - [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) @@ -95,8 +95,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; From e71dbfd730df6a2909deb9b3b53ba027f9f45e29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:37:48 +0900 Subject: [PATCH 287/400] Add inline comment regarding remaining issues with classic scoring --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 41e16ebeaf..184a94912a 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -95,6 +95,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) + // TODO: The following two cases don't match expectations currently (a single hit is registered in acc portion when it shouldn't be). See https://github.com/ppy/osu/issues/12604. [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) From 48d6c9ac4bee70169ac982611545c9d276126988 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:47:30 +0900 Subject: [PATCH 288/400] Move snap/divisor helper methods to inside `ControlPointInfo` --- .../NonVisual/ClosestBeatDivisorTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 26 ------------ .../ControlPoints/ControlPointInfo.cs | 42 +++++++++++++++++++ osu.Game/Beatmaps/IBeatmap.cs | 22 ---------- .../Edit/Checks/CheckUnsnappedObjects.cs | 8 ++-- osu.Game/Screens/Edit/EditorBeatmap.cs | 11 +---- osu.Game/Screens/Play/GameplayBeatmap.cs | 9 ---- 7 files changed, 49 insertions(+), 71 deletions(-) diff --git a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs index 4d6986f5d2..5ac121f5bc 100644 --- a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs +++ b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.NonVisual }; for (int i = 0; i < divisors.Count; ++i) - Assert.AreEqual(closestDivisors[i], beatmap.ClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}"); + Assert.AreEqual(closestDivisors[i], beatmap.ControlPointInfo.ClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}"); } } } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 6515540527..e5b6a4bc44 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -9,7 +9,6 @@ using System.Linq; using osu.Game.Beatmaps.ControlPoints; using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; -using osu.Game.Screens.Edit; namespace osu.Game.Beatmaps { @@ -75,31 +74,6 @@ namespace osu.Game.Beatmaps return mostCommon.beatLength; } - public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) - { - var timingPoint = ControlPointInfo.TimingPointAt(referenceTime ?? time); - var beatLength = timingPoint.BeatLength / beatDivisor; - var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); - - // Casting to int matches stable. - return (int)(timingPoint.Time + beatLengths * beatLength); - } - - public int ClosestSnapTime(double time, double? referenceTime = null) - { - return ClosestSnapTime(time, ClosestBeatDivisor(time, referenceTime), referenceTime); - } - - public int ClosestBeatDivisor(double time, double? referenceTime = null) - { - double getUnsnap(int divisor) => Math.Abs(time - ClosestSnapTime(time, divisor, referenceTime)); - - int[] divisors = BindableBeatDivisor.VALID_DIVISORS; - double smallestUnsnap = divisors.Min(getUnsnap); - - return divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); - } - IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 5cc60a5758..d1a04061b9 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -7,6 +7,7 @@ using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Lists; +using osu.Game.Screens.Edit; namespace osu.Game.Beatmaps.ControlPoints { @@ -160,6 +161,47 @@ namespace osu.Game.Beatmaps.ControlPoints groups.Remove(group); } + /// + /// Returns the time on the given beat divisor closest to the given time. + /// + /// The time to find the closest snapped time to. + /// The beat divisor to snap to. + /// An optional reference point to use for timing point lookup. + public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) + { + var timingPoint = TimingPointAt(referenceTime ?? time); + var beatLength = timingPoint.BeatLength / beatDivisor; + var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + + // Casting to int matches stable. + return (int)(timingPoint.Time + beatLengths * beatLength); + } + + /// + /// Returns the time on any valid beat divisor closest to the given time. + /// + /// The time to find the closest snapped time to. + /// An optional reference point to use for timing point lookup. + public int ClosestSnapTime(double time, double? referenceTime = null) + { + return ClosestSnapTime(time, ClosestBeatDivisor(time, referenceTime), referenceTime); + } + + /// + /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned. + /// + /// The time to find the closest beat snap divisor to. + /// An optional reference point to use for timing point lookup. + public int ClosestBeatDivisor(double time, double? referenceTime = null) + { + double getUnsnap(int divisor) => Math.Abs(time - ClosestSnapTime(time, divisor, referenceTime)); + + int[] divisors = BindableBeatDivisor.VALID_DIVISORS; + double smallestUnsnap = divisors.Min(getUnsnap); + + return divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); + } + /// /// Binary searches one of the control point lists to find the active control point at . /// Includes logic for returning a specific point when no matching point is found. diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 679d639fd1..769b33009a 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -51,28 +51,6 @@ namespace osu.Game.Beatmaps /// double GetMostCommonBeatLength(); - /// - /// Returns the time on the given beat divisor closest to the given time. - /// - /// The time to find the closest snapped time to. - /// The beat divisor to snap to. - /// An optional reference point to use for timing point lookup. - int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null); - - /// - /// Returns the time on any valid beat divisor closest to the given time. - /// - /// The time to find the closest snapped time to. - /// An optional reference point to use for timing point lookup. - int ClosestSnapTime(double time, double? referenceTime = null); - - /// - /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned. - /// - /// The time to find the closest beat snap divisor to. - /// An optional reference point to use for timing point lookup. - int ClosestBeatDivisor(double time, double? referenceTime = null); - /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index 8b6bb7d461..cc5ea2a988 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -24,9 +24,11 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { + var controlPointInfo = playableBeatmap.ControlPointInfo; + foreach (var hitobject in playableBeatmap.HitObjects) { - double startUnsnap = hitobject.StartTime - playableBeatmap.ClosestSnapTime(hitobject.StartTime); + double startUnsnap = hitobject.StartTime - controlPointInfo.ClosestSnapTime(hitobject.StartTime); string startPostfix = hitobject is IHasDuration ? "start" : ""; foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix)) yield return issue; @@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1); double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1); - double repeatUnsnap = repeatTime - playableBeatmap.ClosestSnapTime(repeatTime); + double repeatUnsnap = repeatTime - controlPointInfo.ClosestSnapTime(repeatTime); foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat")) yield return issue; } @@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitobject is IHasDuration hasDuration) { - double endUnsnap = hasDuration.EndTime - playableBeatmap.ClosestSnapTime(hasDuration.EndTime); + double endUnsnap = hasDuration.EndTime - controlPointInfo.ClosestSnapTime(hasDuration.EndTime); foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end")) yield return issue; } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 72fb0ac9e9..f1262daab3 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -301,16 +301,7 @@ namespace osu.Game.Screens.Edit return list.Count - 1; } - public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) - { - return PlayableBeatmap.ClosestSnapTime(time, beatDivisor, referenceTime); - } - - public int ClosestSnapTime(double time, double? referenceTime = null) => PlayableBeatmap.ClosestSnapTime(time, referenceTime); - - public int ClosestBeatDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatDivisor(time, referenceTime); - - public double SnapTime(double time, double? referenceTime) => ClosestSnapTime(time, BeatDivisor, referenceTime); + public double SnapTime(double time, double? referenceTime) => ControlPointInfo.ClosestSnapTime(time, BeatDivisor, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs index 92f58c8759..74fbe540fa 100644 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ b/osu.Game/Screens/Play/GameplayBeatmap.cs @@ -45,15 +45,6 @@ namespace osu.Game.Screens.Play public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); - public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) - { - return PlayableBeatmap.ClosestSnapTime(time, beatDivisor, referenceTime); - } - - public int ClosestSnapTime(double time, double? referenceTime = null) => PlayableBeatmap.ClosestSnapTime(time, referenceTime); - - public int ClosestBeatDivisor(double time, double? referenceTime = null) => PlayableBeatmap.ClosestBeatDivisor(time, referenceTime); - public IBeatmap Clone() => PlayableBeatmap.Clone(); private readonly Bindable lastJudgementResult = new Bindable(); From f3c7694eeb8f78cd6368fd5701e22ed291e000ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:57:52 +0900 Subject: [PATCH 289/400] Rename methods to match generally how these find-methods are named elsewhere --- osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs | 2 +- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 10 +++++----- osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs | 6 +++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs index 5ac121f5bc..08cd80dcfa 100644 --- a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs +++ b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.NonVisual }; for (int i = 0; i < divisors.Count; ++i) - Assert.AreEqual(closestDivisors[i], beatmap.ControlPointInfo.ClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}"); + Assert.AreEqual(closestDivisors[i], beatmap.ControlPointInfo.GetClosestBeatDivisor(beatmap.HitObjects[i].StartTime), $"at index {i}"); } } } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index d1a04061b9..60a8a40f06 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -167,7 +167,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the closest snapped time to. /// The beat divisor to snap to. /// An optional reference point to use for timing point lookup. - public int ClosestSnapTime(double time, int beatDivisor, double? referenceTime = null) + public int GetClosestSnappedTime(double time, int beatDivisor, double? referenceTime = null) { var timingPoint = TimingPointAt(referenceTime ?? time); var beatLength = timingPoint.BeatLength / beatDivisor; @@ -182,9 +182,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the closest snapped time to. /// An optional reference point to use for timing point lookup. - public int ClosestSnapTime(double time, double? referenceTime = null) + public int GetClosestSnappedTime(double time, double? referenceTime = null) { - return ClosestSnapTime(time, ClosestBeatDivisor(time, referenceTime), referenceTime); + return GetClosestSnappedTime(time, GetClosestBeatDivisor(time, referenceTime), referenceTime); } /// @@ -192,9 +192,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the closest beat snap divisor to. /// An optional reference point to use for timing point lookup. - public int ClosestBeatDivisor(double time, double? referenceTime = null) + public int GetClosestBeatDivisor(double time, double? referenceTime = null) { - double getUnsnap(int divisor) => Math.Abs(time - ClosestSnapTime(time, divisor, referenceTime)); + double getUnsnap(int divisor) => Math.Abs(time - GetClosestSnappedTime(time, divisor, referenceTime)); int[] divisors = BindableBeatDivisor.VALID_DIVISORS; double smallestUnsnap = divisors.Min(getUnsnap); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index cc5ea2a988..cdf3f05465 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Edit.Checks foreach (var hitobject in playableBeatmap.HitObjects) { - double startUnsnap = hitobject.StartTime - controlPointInfo.ClosestSnapTime(hitobject.StartTime); + double startUnsnap = hitobject.StartTime - controlPointInfo.GetClosestSnappedTime(hitobject.StartTime); string startPostfix = hitobject is IHasDuration ? "start" : ""; foreach (var issue in getUnsnapIssues(hitobject, startUnsnap, hitobject.StartTime, startPostfix)) yield return issue; @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Edit.Checks { double spanDuration = hasRepeats.Duration / (hasRepeats.RepeatCount + 1); double repeatTime = hitobject.StartTime + spanDuration * (repeatIndex + 1); - double repeatUnsnap = repeatTime - controlPointInfo.ClosestSnapTime(repeatTime); + double repeatUnsnap = repeatTime - controlPointInfo.GetClosestSnappedTime(repeatTime); foreach (var issue in getUnsnapIssues(hitobject, repeatUnsnap, repeatTime, "repeat")) yield return issue; } @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (hitobject is IHasDuration hasDuration) { - double endUnsnap = hasDuration.EndTime - controlPointInfo.ClosestSnapTime(hasDuration.EndTime); + double endUnsnap = hasDuration.EndTime - controlPointInfo.GetClosestSnappedTime(hasDuration.EndTime); foreach (var issue in getUnsnapIssues(hitobject, endUnsnap, hasDuration.EndTime, "end")) yield return issue; } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index f1262daab3..be53abbd55 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Edit return list.Count - 1; } - public double SnapTime(double time, double? referenceTime) => ControlPointInfo.ClosestSnapTime(time, BeatDivisor, referenceTime); + public double SnapTime(double time, double? referenceTime) => ControlPointInfo.GetClosestSnappedTime(time, BeatDivisor, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; From c5186b6a693e7ff3ff1b7737a5b4f81ba0cf1124 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:59:49 +0900 Subject: [PATCH 290/400] Revert return values to non-rounded doubles --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 60a8a40f06..fa1c59bb08 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -167,14 +167,13 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the closest snapped time to. /// The beat divisor to snap to. /// An optional reference point to use for timing point lookup. - public int GetClosestSnappedTime(double time, int beatDivisor, double? referenceTime = null) + public double GetClosestSnappedTime(double time, int beatDivisor, double? referenceTime = null) { var timingPoint = TimingPointAt(referenceTime ?? time); var beatLength = timingPoint.BeatLength / beatDivisor; var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); - // Casting to int matches stable. - return (int)(timingPoint.Time + beatLengths * beatLength); + return timingPoint.Time + beatLengths * beatLength; } /// @@ -182,7 +181,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time to find the closest snapped time to. /// An optional reference point to use for timing point lookup. - public int GetClosestSnappedTime(double time, double? referenceTime = null) + public double GetClosestSnappedTime(double time, double? referenceTime = null) { return GetClosestSnappedTime(time, GetClosestBeatDivisor(time, referenceTime), referenceTime); } From 859898d98f09b9f10e928eca51206be5519b3fbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 17:16:05 +0900 Subject: [PATCH 291/400] Refactor lookup methods to avoid linq and reduce `TimingPointAt` calls --- .../ControlPoints/ControlPointInfo.cs | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index fa1c59bb08..e47d48edcf 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -170,35 +170,47 @@ namespace osu.Game.Beatmaps.ControlPoints public double GetClosestSnappedTime(double time, int beatDivisor, double? referenceTime = null) { var timingPoint = TimingPointAt(referenceTime ?? time); - var beatLength = timingPoint.BeatLength / beatDivisor; - var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); - - return timingPoint.Time + beatLengths * beatLength; + return getClosestSnappedTime(timingPoint, time, beatDivisor); } /// - /// Returns the time on any valid beat divisor closest to the given time. + /// Returns the time on *ANY* valid beat divisor, favouring the divisor closest to the given time. /// /// The time to find the closest snapped time to. - /// An optional reference point to use for timing point lookup. - public double GetClosestSnappedTime(double time, double? referenceTime = null) - { - return GetClosestSnappedTime(time, GetClosestBeatDivisor(time, referenceTime), referenceTime); - } + public double GetClosestSnappedTime(double time) => GetClosestSnappedTime(time, GetClosestBeatDivisor(time)); /// - /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest is returned. + /// Returns the beat snap divisor closest to the given time. If two are equally close, the smallest divisor is returned. /// /// The time to find the closest beat snap divisor to. /// An optional reference point to use for timing point lookup. public int GetClosestBeatDivisor(double time, double? referenceTime = null) { - double getUnsnap(int divisor) => Math.Abs(time - GetClosestSnappedTime(time, divisor, referenceTime)); + TimingControlPoint timingPoint = TimingPointAt(referenceTime ?? time); - int[] divisors = BindableBeatDivisor.VALID_DIVISORS; - double smallestUnsnap = divisors.Min(getUnsnap); + int closestDivisor = 0; + double closestTime = double.MaxValue; - return divisors.FirstOrDefault(divisor => getUnsnap(divisor) == smallestUnsnap); + foreach (int divisor in BindableBeatDivisor.VALID_DIVISORS) + { + double distanceFromSnap = Math.Abs(time - getClosestSnappedTime(timingPoint, time, divisor)); + + if (distanceFromSnap < closestTime) + { + closestDivisor = divisor; + closestTime = distanceFromSnap; + } + } + + return closestDivisor; + } + + private static double getClosestSnappedTime(TimingControlPoint timingPoint, double time, int beatDivisor) + { + var beatLength = timingPoint.BeatLength / beatDivisor; + var beatLengths = (int)Math.Round((time - timingPoint.Time) / beatLength, MidpointRounding.AwayFromZero); + + return timingPoint.Time + beatLengths * beatLength; } /// From 126056c43626cfbe5fcb9906ceb8d8e8425029a9 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Wed, 28 Apr 2021 19:27:18 +0800 Subject: [PATCH 292/400] Fix precision loss on exporting legacy replays --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 56c4e75864..144aeeebce 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -95,8 +95,9 @@ namespace osu.Game.Scoring.Legacy foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { - replayData.Append(FormattableString.Invariant($"{(int)Math.Round(f.Time - lastF.Time)}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); + replayData.Append(FormattableString.Invariant($"{Math.Round(f.Time - lastF.Time)}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); lastF = f; + lastF.Time = Math.Round(f.Time); } } From 4fe1497f63ec6f65badc27505821a311690226db Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Wed, 28 Apr 2021 20:23:56 +0800 Subject: [PATCH 293/400] Add comment & remove lastF --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 144aeeebce..13876f1648 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -91,13 +91,13 @@ namespace osu.Game.Scoring.Legacy if (score.Replay != null) { - LegacyReplayFrame lastF = new LegacyReplayFrame(0, 0, 0, ReplayButtonState.None); - + int lastTimeRounded = 0; foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { - replayData.Append(FormattableString.Invariant($"{Math.Round(f.Time - lastF.Time)}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); - lastF = f; - lastF.Time = Math.Round(f.Time); + // Rounding because stable could only parse integral values + int timeRounded = (int)Math.Round(f.Time); + replayData.Append(FormattableString.Invariant($"{timeRounded - lastTimeRounded}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); + lastTimeRounded = timeRounded; } } From 6d7eef3f5a437e1886908f3440138700e0dcd9e2 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 28 Apr 2021 16:45:57 +0000 Subject: [PATCH 294/400] Upgrade to GitHub-native Dependabot --- .github/dependabot.yml | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..9e9af23b27 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,46 @@ +version: 2 +updates: +- package-ecosystem: nuget + directory: "/" + schedule: + interval: monthly + time: "17:00" + open-pull-requests-limit: 99 + ignore: + - dependency-name: Microsoft.EntityFrameworkCore.Design + versions: + - "> 2.2.6" + - dependency-name: Microsoft.EntityFrameworkCore.Sqlite + versions: + - "> 2.2.6" + - dependency-name: Microsoft.EntityFrameworkCore.Sqlite.Core + versions: + - "> 2.2.6" + - dependency-name: Microsoft.Extensions.DependencyInjection + versions: + - ">= 5.a, < 6" + - dependency-name: NUnit3TestAdapter + versions: + - ">= 3.16.a, < 3.17" + - dependency-name: Microsoft.NET.Test.Sdk + versions: + - 16.9.1 + - dependency-name: Microsoft.Extensions.DependencyInjection + versions: + - 3.1.11 + - 3.1.12 + - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson + versions: + - 3.1.11 + - dependency-name: Microsoft.NETCore.Targets + versions: + - 5.0.0 + - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.MessagePack + versions: + - 5.0.2 + - dependency-name: NUnit + versions: + - 3.13.1 + - dependency-name: Microsoft.AspNetCore.SignalR.Client + versions: + - 3.1.11 From 243605728d5f167680caa5bbb0dee1225960075d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Apr 2021 11:44:05 -0700 Subject: [PATCH 295/400] Fix approved maps not displaying pp column on score table --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index b598b7d97f..3e95d125de 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -59,8 +59,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); var topScore = scoreInfos.First(); + var status = topScore.Beatmap?.Status; + var showPerformanceColumn = status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved; - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked); + scoreTable.DisplayScores(scoreInfos, showPerformanceColumn); scoreTable.Show(); var userScore = value.UserScore; From 921d4510478943f5b6c195044d30102f2a9f2fe2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:50:49 +0000 Subject: [PATCH 296/400] Bump Microsoft.AspNetCore.SignalR.Client from 5.0.4 to 5.0.5 Bumps [Microsoft.AspNetCore.SignalR.Client](https://github.com/dotnet/aspnetcore) from 5.0.4 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.4...v5.0.5) Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..c61c59e589 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + From 5720e255c9b3ed3c1d466db57ca4eac65fae0a09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:50:53 +0000 Subject: [PATCH 297/400] Bump Sentry from 3.2.0 to 3.3.4 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 3.2.0 to 3.3.4. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Changelog](https://github.com/getsentry/sentry-dotnet/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/3.2.0...3.3.4) Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..abb1e40b64 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + From 6c51bf523ca0740feae42ade648b7bbffa49192d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:50:56 +0000 Subject: [PATCH 298/400] Bump SharpCompress from 0.28.1 to 0.28.2 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.28.1 to 0.28.2. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.28.1...0.28.2) Signed-off-by: dependabot[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 986bd8e7ba..189bc724fc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -32,7 +32,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c32109d6db..1a9f945ec8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -94,7 +94,7 @@ - + From 437e9201ba844fb6d7baac65ea679647d439b370 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:00 +0000 Subject: [PATCH 299/400] Bump Microsoft.AspNetCore.SignalR.Protocols.MessagePack Bumps [Microsoft.AspNetCore.SignalR.Protocols.MessagePack](https://github.com/dotnet/aspnetcore) from 5.0.4 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.4...v5.0.5) Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..e2c8e2cf1b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + From 5b071c68e7ab85f7e7faab2e2ae79762140ba6ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:04 +0000 Subject: [PATCH 300/400] Bump Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson Bumps [Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson](https://github.com/dotnet/aspnetcore) from 5.0.4 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.4...v5.0.5) Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..c9de4dd28a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + From 1e7feff49d57fc2f0575a8a423083e0ff9a44d65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:08 +0000 Subject: [PATCH 301/400] Bump Humanizer from 2.8.26 to 2.9.9 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.8.26 to 2.9.9. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/main/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.8.26...v2.9.9) Signed-off-by: dependabot[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 986bd8e7ba..aa1599e69a 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 c32109d6db..7061bb5e84 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -89,7 +89,7 @@ - + From 1b3b07d6a9f0ccb9e595a1c6ae69456254356b96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:12 +0000 Subject: [PATCH 302/400] Bump NUnit from 3.13.1 to 3.13.2 Bumps [NUnit](https://github.com/nunit/nunit) from 3.13.1 to 3.13.2. - [Release notes](https://github.com/nunit/nunit/releases) - [Changelog](https://github.com/nunit/nunit/blob/v3.13.2/CHANGES.md) - [Commits](https://github.com/nunit/nunit/compare/v3.13.1...v3.13.2) Signed-off-by: dependabot[bot] --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- 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 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 98a32f9b3a..992f954a3a 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index afa7b03536..7571d1827a 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index c9f87a8551..1c8ed54440 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index afa7b03536..7571d1827a 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index ea43d9a54c..bfcf4ef35e 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -8,7 +8,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 c2d9a923d9..77e9d672e3 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 @@ -3,7 +3,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 64e934efd2..8f8b99b092 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 @@ -3,7 +3,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 f743d65db3..e01e858873 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 @@ -3,7 +3,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 eab144592f..2dfa1dfbb7 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 @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index df6d17f615..895518e1b9 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index a4e52f8cd4..d5dda39aa5 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..b242811939 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -33,7 +33,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c32109d6db..423f87923b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -95,7 +95,7 @@ - + From 9c62c90cfc441a2688afbefb636f430aeea534b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 15:29:25 +0900 Subject: [PATCH 303/400] Refactor `SelectionBlueprint` and `MoveSelectionEvent` to work in screen-space coordinates Until now, the implementation of the overrides in `SelectionBlueprint` have been confusing to the point where I would just implement by trial-and-error (or copying from an existing implementation). This was due to a combination of using "object" space coordinates (ie. the thing the `Blueprint` is operating on) and screen-space coordinates. This change switches all event related coordinates to screen-space, which is how we already handle rotation/scale operations. With the introduction of other editor types where the related objects are drawables, this also makes a lot more sense. --- .../Edit/ManiaSelectionHandler.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 3 ++- osu.Game/Extensions/DrawableExtensions.cs | 11 +++++++++++ .../Rulesets/Edit/OverlaySelectionBlueprint.cs | 2 -- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 4 +--- .../Edit/Compose/Components/BlueprintContainer.cs | 11 +++++++---- .../Components/ComposeBlueprintContainer.cs | 2 +- .../Edit/Compose/Components/MoveSelectionEvent.cs | 15 ++++----------- 8 files changed, 27 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index dd059c967c..7042110423 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Edit { var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield; - var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.ScreenSpacePosition); + var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.Blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta); if (currentColumn == null) return; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 5164c7f204..c2c1f6d602 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; +using osu.Game.Extensions; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; @@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit // this will potentially move the selection out of bounds... foreach (var h in hitObjects) - h.Position += moveEvent.InstantDelta; + h.Position += this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); // but this will be corrected. moveSelectionInBounds(); diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 67b9e727a5..a8de3f6407 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Threading; +using osuTK; namespace osu.Game.Extensions { @@ -32,5 +34,14 @@ namespace osu.Game.Extensions scheduler.Add(repeatDelegate); return repeatDelegate; } + + /// + /// Accepts a delta vector in screen-space coordinates and converts it to one which can be applied to this drawable's position. + /// + /// The drawable. + /// A delta in screen-space coordinates. + /// The delta vector in Parent's coordinates. + public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) => + drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta); } } diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs index 6369112d80..7911cf874b 100644 --- a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs @@ -34,7 +34,5 @@ namespace osu.Game.Rulesets.Edit public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; - - public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => DrawableObject.Parent.ToLocalSpace(screenSpacePosition) - DrawableObject.Position; } } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index b176ecb148..55703a2cd3 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Edit public virtual MenuItem[] ContextMenuItems => Array.Empty(); /// - /// The screen-space point that causes this to be selected. + /// The screen-space point that causes this to be selected via a drag. /// public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre; @@ -136,8 +136,6 @@ namespace osu.Game.Rulesets.Edit /// public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; - public virtual Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; - /// /// Handle to perform a partial deletion when the user requests a quick delete (Shift+Right Click). /// diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 1f9cd0258e..361e98e0dd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -436,14 +436,17 @@ namespace osu.Game.Screens.Edit.Compose.Components // check for positional snap for every object in selection (for things like object-object snapping) for (var i = 0; i < movementBlueprintOriginalPositions.Length; i++) { - var testPosition = movementBlueprintOriginalPositions[i] + distanceTravelled; + Vector2 originalPosition = movementBlueprintOriginalPositions[i]; + var testPosition = originalPosition + distanceTravelled; var positionalResult = snapProvider.SnapScreenSpacePositionToValidPosition(testPosition); if (positionalResult.ScreenSpacePosition == testPosition) continue; + var delta = positionalResult.ScreenSpacePosition - movementBlueprints[i].ScreenSpaceSelectionPoint; + // attempt to move the objects, and abort any time based snapping if we can. - if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], positionalResult.ScreenSpacePosition))) + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], delta))) return true; } } @@ -459,14 +462,14 @@ namespace osu.Game.Screens.Edit.Compose.Components if (result == null) { - return SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), movePosition)); + return SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints.First(), movePosition - movementBlueprints.First().ScreenSpaceSelectionPoint)); } return ApplySnapResult(movementBlueprints, result); } protected virtual bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) => - SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition)); + SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition - blueprints.First().ScreenSpaceSelectionPoint)); /// /// Finishes the current movement of selected blueprints. diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index f8ac0552ae..6c174e563e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // convert to game space coordinates delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero); - SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, firstBlueprint.ScreenSpaceSelectionPoint + delta)); + SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, delta)); } private void updatePlacementNewCombo() diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index a32c30b579..4d4f4b76c6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -17,21 +17,14 @@ namespace osu.Game.Screens.Edit.Compose.Components public readonly SelectionBlueprint Blueprint; /// - /// The expected screen-space position of the blueprint's item at the current cursor position. + /// The screen-space delta of this move event. /// - public readonly Vector2 ScreenSpacePosition; + public readonly Vector2 ScreenSpaceDelta; - /// - /// The distance between and the blueprint's current position, in the coordinate-space of the blueprint item's parent. - /// - public readonly Vector2 InstantDelta; - - public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpacePosition) + public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpaceDelta) { Blueprint = blueprint; - ScreenSpacePosition = screenSpacePosition; - - InstantDelta = Blueprint.GetInstantDelta(ScreenSpacePosition); + ScreenSpaceDelta = screenSpaceDelta; } } } From 2d17219c8fa8dd28de771604a12fd618e3cab6e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 19:56:49 +0900 Subject: [PATCH 304/400] Setup basic test and classes for scale adjustment --- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 42 +++++++++++++++++++ .../Play/HUD/SkinnableAccuracyCounter.cs | 2 +- .../Screens/Play/HUD/SkinnableHUDComponent.cs | 32 ++++++++++++++ .../Screens/Play/HUD/SkinnableScoreCounter.cs | 2 +- 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index fec1610160..029bf129c8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -4,17 +4,21 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -32,6 +36,32 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuConfigManager config { get; set; } + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.3f, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.7f, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = createSkinSourceComponents(), + }, + } + }); + } + [Test] public void TestComboCounterIncrementing() { @@ -74,6 +104,18 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent); } + private IReadOnlyList createSkinSourceComponents() + { + var hudComponents = typeof(SkinnableHUDComponent).Assembly.GetTypes().Where(t => typeof(SkinnableHUDComponent).IsAssignableFrom(t)).ToArray(); + + List drawables = new List(); + + foreach (var component in hudComponents) + drawables.AddRange(component.CreateSettingsControls()); + + return drawables; + } + private void createNew(Action action = null) { AddStep("create overlay", () => diff --git a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs index 76c9c30813..ebb3bcd148 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs @@ -6,7 +6,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableAccuracyCounter : SkinnableDrawable, IAccuracyCounter + public class SkinnableAccuracyCounter : SkinnableHUDComponent, IAccuracyCounter { public Bindable Current { get; } = new Bindable(); diff --git a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs new file mode 100644 index 0000000000..ea14a15348 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// A skinnable HUD component which can be scaled and repositioned at a skinner/user's will. + /// + public abstract class SkinnableHUDComponent : SkinnableDrawable + { + [SettingSource("Scale", "The scale at which this component should be displayed.")] + public BindableNumber SkinScale { get; } = new BindableFloat(1) + { + Precision = 0.1f, + MinValue = 0.1f, + MaxValue = 10, + Default = 1, + Value = 1, + }; + + protected SkinnableHUDComponent(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) + : base(component, defaultImplementation, allowFallback, confineMode) + { + } + } +} diff --git a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs index b46f5684b1..9165b2c7f0 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs @@ -10,7 +10,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableScoreCounter : SkinnableDrawable, IScoreCounter + public class SkinnableScoreCounter : SkinnableHUDComponent, IScoreCounter { public Bindable Current { get; } = new Bindable(); From fca173225a2a8e8bf032f2e1019d197a64af1023 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 15:40:35 +0900 Subject: [PATCH 305/400] Refactor editor selection/blueprint components to be generic --- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 17 ++++++++++++++--- .../Screens/Play/HUD/SkinnableComboCounter.cs | 2 +- .../Screens/Play/HUD/SkinnableHealthDisplay.cs | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 029bf129c8..4f8a78368e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -13,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Configuration; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Colour = Color4.Black, RelativeSizeAxes = Axes.Both, - Alpha = 0.7f, + Alpha = 0.9f, }, new FillFlowContainer { @@ -106,12 +106,23 @@ namespace osu.Game.Tests.Visual.Gameplay private IReadOnlyList createSkinSourceComponents() { - var hudComponents = typeof(SkinnableHUDComponent).Assembly.GetTypes().Where(t => typeof(SkinnableHUDComponent).IsAssignableFrom(t)).ToArray(); + Drawable[] hudComponents = typeof(SkinnableHUDComponent).Assembly + .GetTypes() + .Where(t => typeof(SkinnableHUDComponent).IsAssignableFrom(t)) + .Where(t => !t.IsAbstract) + .Select(t => Activator.CreateInstance(t) as Drawable) + .ToArray(); List drawables = new List(); foreach (var component in hudComponents) + { + drawables.Add(new OsuSpriteText + { + Text = component.GetType().Name + }); drawables.AddRange(component.CreateSettingsControls()); + } return drawables; } diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index c04c50141a..629bb467bc 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -6,7 +6,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableComboCounter : SkinnableDrawable, IComboCounter + public class SkinnableComboCounter : SkinnableHUDComponent, IComboCounter { public Bindable Current { get; } = new Bindable(); diff --git a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs index 1f91f5e50f..f8a8696dae 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs @@ -9,7 +9,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay + public class SkinnableHealthDisplay : SkinnableHUDComponent, IHealthDisplay { public Bindable Current { get; } = new BindableDouble(1) { From 99b428ee4b853033abfac1e0ee08a57c185d7a91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 18:03:49 +0900 Subject: [PATCH 306/400] Add very basic skin editor test --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 222 ++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs new file mode 100644 index 0000000000..2391945b91 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -0,0 +1,222 @@ +// 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.Game.Rulesets.Objects.Drawables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; +using osu.Framework.Testing; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinEditor : OsuTestScene + { + private HUDOverlay hudOverlay; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create hud", () => + { + hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + + hudOverlay.ComboCounter.Current.Value = 1; + }); + + AddStep("create editor overlay", () => Add(new SkinEditor(hudOverlay))); + } + + public class SkinEditor : CompositeDrawable + { + private readonly Drawable target; + + public SkinEditor(Drawable target) + { + this.target = target; + + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + InternalChildren = new[] + { + target, + new SkinBlueprintContainer(target), + }; + } + + public class SkinBlueprintContainer : BlueprintContainer + { + private readonly Drawable target; + + public SkinBlueprintContainer(Drawable target) + { + this.target = target; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + SkinnableHUDComponent[] components = target.ChildrenOfType().ToArray(); + + foreach (var c in components) + { + Logger.Log($"Adding blueprint for {c.GetType()}"); + AddBlueprintFor(c); + } + } + + protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); + + public class SkinSelectionHandler : SelectionHandler + { + protected override void DeleteItems(IEnumerable items) + { + foreach (var i in items) + i.Drawable.Expire(); + } + + protected override void OnSelectionChanged() + { + base.OnSelectionChanged(); + + SelectionBox.CanRotate = true; + SelectionBox.CanScaleX = true; + SelectionBox.CanScaleY = true; + SelectionBox.CanReverse = false; + } + + public override bool HandleRotation(float angle) + { + foreach (var c in SelectedBlueprints) + c.Item.Rotation += angle; + + return base.HandleRotation(angle); + } + + public override bool HandleScale(Vector2 scale, Anchor anchor) + { + adjustScaleFromAnchor(ref scale, anchor); + + foreach (var c in SelectedBlueprints) + c.Item.Scale += scale * 0.01f; + + return true; + } + + private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) + { + // cancel out scale in axes we don't care about (based on which drag handle was used). + if ((reference & Anchor.x1) > 0) scale.X = 0; + if ((reference & Anchor.y1) > 0) scale.Y = 0; + + // reverse the scale direction if dragging from top or left. + if ((reference & Anchor.x0) > 0) scale.X = -scale.X; + if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; + } + + public override bool HandleMovement(MoveSelectionEvent moveEvent) + { + foreach (var c in SelectedBlueprints) + c.Item.Position += moveEvent.InstantDelta; + return true; + } + } + + protected override SelectionBlueprint CreateBlueprintFor(SkinnableHUDComponent component) + => new SkinBlueprint(component); + + public class SkinBlueprint : SelectionBlueprint + { + /// + /// The which this applies to. + /// + public readonly SkinnableHUDComponent Component; + + private Container box; + private Drawable drawable => Component.Drawable; + + /// + /// Whether the blueprint should be shown even when the is not alive. + /// + protected virtual bool AlwaysShowWhenSelected => false; + + protected override bool ShouldBeAlive => (Component.IsAlive && Component.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); + + public SkinBlueprint(SkinnableHUDComponent component) + : base(component) + { + Component = component; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + box = new Container + { + Colour = colours.Yellow, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + AlwaysPresent = true, + }, + } + }, + }; + } + + private Quad drawableQuad; + + public override Quad ScreenSpaceDrawQuad => drawableQuad; + + protected override void Update() + { + base.Update(); + + drawableQuad = drawable.ScreenSpaceDrawQuad; + var quad = ToLocalSpace(drawable.ScreenSpaceDrawQuad); + + box.Position = quad.TopLeft; + box.Size = quad.Size; + box.Rotation = Component.Rotation; + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); + + public override Vector2 ScreenSpaceSelectionPoint => Component.ToScreenSpace(Vector2.Zero); + + public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; + + public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Component.Parent.ToLocalSpace(screenSpacePosition) - Component.Position; + } + } + } + } +} From 74fb7cd180051a928cb714e291de09188beec55c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 14:21:07 +0900 Subject: [PATCH 307/400] Extract storable attributes to bindables --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 18 +++++----- .../Screens/Play/HUD/SkinnableHUDComponent.cs | 33 ++++++++++++++----- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 2391945b91..4594db36ae 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override bool HandleRotation(float angle) { foreach (var c in SelectedBlueprints) - c.Item.Rotation += angle; + c.Item.SkinRotation.Value += angle; return base.HandleRotation(angle); } @@ -121,11 +121,18 @@ namespace osu.Game.Tests.Visual.Gameplay adjustScaleFromAnchor(ref scale, anchor); foreach (var c in SelectedBlueprints) - c.Item.Scale += scale * 0.01f; + c.Item.SkinScale.Value += scale.X * 0.01f; return true; } + public override bool HandleMovement(MoveSelectionEvent moveEvent) + { + foreach (var c in SelectedBlueprints) + c.Item.SkinPosition.Value += moveEvent.InstantDelta.X; + return true; + } + private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) { // cancel out scale in axes we don't care about (based on which drag handle was used). @@ -136,13 +143,6 @@ namespace osu.Game.Tests.Visual.Gameplay if ((reference & Anchor.x0) > 0) scale.X = -scale.X; if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; } - - public override bool HandleMovement(MoveSelectionEvent moveEvent) - { - foreach (var c in SelectedBlueprints) - c.Item.Position += moveEvent.InstantDelta; - return true; - } } protected override SelectionBlueprint CreateBlueprintFor(SkinnableHUDComponent component) diff --git a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs index ea14a15348..5b8d313400 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs @@ -4,8 +4,10 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Layout; using osu.Game.Configuration; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Screens.Play.HUD { @@ -15,18 +17,33 @@ namespace osu.Game.Screens.Play.HUD public abstract class SkinnableHUDComponent : SkinnableDrawable { [SettingSource("Scale", "The scale at which this component should be displayed.")] - public BindableNumber SkinScale { get; } = new BindableFloat(1) - { - Precision = 0.1f, - MinValue = 0.1f, - MaxValue = 10, - Default = 1, - Value = 1, - }; + public BindableNumber SkinScale { get; } = new BindableFloat(1); + + [SettingSource("Position", "The position at which this component should be displayed.")] + public BindableNumber SkinPosition { get; } = new BindableFloat(); + + [SettingSource("Rotation", "The rotation at which this component should be displayed.")] + public BindableNumber SkinRotation { get; } = new BindableFloat(); + + [SettingSource("Anchor", "The screen edge this component should align to.")] + public Bindable SkinAnchor { get; } = new Bindable(); protected SkinnableHUDComponent(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(component, defaultImplementation, allowFallback, confineMode) { + SkinScale.BindValueChanged(scale => Drawable.Scale = new Vector2(scale.NewValue)); + SkinPosition.BindValueChanged(position => Position = new Vector2(position.NewValue)); + SkinRotation.BindValueChanged(rotation => Drawable.Rotation = rotation.NewValue); + SkinAnchor.BindValueChanged(anchor => Anchor = anchor.NewValue); + } + + protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) + { + SkinScale.Value = Drawable.Scale.X; + SkinPosition.Value = Position.X; + SkinRotation.Value = Drawable.Rotation; + SkinAnchor.Value = Anchor; + return base.OnInvalidate(invalidation, source); } } } From 1cb8fc9a241a2d07d43019c67f2b7312cc3aa69b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 15:14:48 +0900 Subject: [PATCH 308/400] Extract editor classes out of test namespace and add anchor support --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 213 ++---------------- .../Screens/Play/HUD/SkinnableHUDComponent.cs | 5 +- osu.Game/Skinning/Editor/SkinBlueprint.cs | 86 +++++++ .../Skinning/Editor/SkinBlueprintContainer.cs | 36 +++ osu.Game/Skinning/Editor/SkinEditor.cs | 36 +++ .../Skinning/Editor/SkinSelectionHandler.cs | 139 ++++++++++++ 6 files changed, 315 insertions(+), 200 deletions(-) create mode 100644 osu.Game/Skinning/Editor/SkinBlueprint.cs create mode 100644 osu.Game/Skinning/Editor/SkinBlueprintContainer.cs create mode 100644 osu.Game/Skinning/Editor/SkinEditor.cs create mode 100644 osu.Game/Skinning/Editor/SkinSelectionHandler.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 4594db36ae..e46cd70667 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -2,221 +2,36 @@ // 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.Game.Rulesets.Objects.Drawables; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Logging; using osu.Framework.Testing; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD; -using osuTK; +using osu.Game.Skinning.Editor; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneSkinEditor : OsuTestScene + public class TestSceneSkinEditor : SkinnableTestScene { - private HUDOverlay hudOverlay; - [SetUpSteps] public void SetUpSteps() { - AddStep("create hud", () => + AddStep("create editor overlay", () => { - hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + SetContents(() => + { + var hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); - // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + hudOverlay.ComboCounter.Current.Value = 1; - hudOverlay.ComboCounter.Current.Value = 1; + return new SkinEditor(hudOverlay); + }); }); - - AddStep("create editor overlay", () => Add(new SkinEditor(hudOverlay))); } - public class SkinEditor : CompositeDrawable - { - private readonly Drawable target; - - public SkinEditor(Drawable target) - { - this.target = target; - - RelativeSizeAxes = Axes.Both; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - InternalChildren = new[] - { - target, - new SkinBlueprintContainer(target), - }; - } - - public class SkinBlueprintContainer : BlueprintContainer - { - private readonly Drawable target; - - public SkinBlueprintContainer(Drawable target) - { - this.target = target; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - SkinnableHUDComponent[] components = target.ChildrenOfType().ToArray(); - - foreach (var c in components) - { - Logger.Log($"Adding blueprint for {c.GetType()}"); - AddBlueprintFor(c); - } - } - - protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); - - public class SkinSelectionHandler : SelectionHandler - { - protected override void DeleteItems(IEnumerable items) - { - foreach (var i in items) - i.Drawable.Expire(); - } - - protected override void OnSelectionChanged() - { - base.OnSelectionChanged(); - - SelectionBox.CanRotate = true; - SelectionBox.CanScaleX = true; - SelectionBox.CanScaleY = true; - SelectionBox.CanReverse = false; - } - - public override bool HandleRotation(float angle) - { - foreach (var c in SelectedBlueprints) - c.Item.SkinRotation.Value += angle; - - return base.HandleRotation(angle); - } - - public override bool HandleScale(Vector2 scale, Anchor anchor) - { - adjustScaleFromAnchor(ref scale, anchor); - - foreach (var c in SelectedBlueprints) - c.Item.SkinScale.Value += scale.X * 0.01f; - - return true; - } - - public override bool HandleMovement(MoveSelectionEvent moveEvent) - { - foreach (var c in SelectedBlueprints) - c.Item.SkinPosition.Value += moveEvent.InstantDelta.X; - return true; - } - - private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) - { - // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((reference & Anchor.x1) > 0) scale.X = 0; - if ((reference & Anchor.y1) > 0) scale.Y = 0; - - // reverse the scale direction if dragging from top or left. - if ((reference & Anchor.x0) > 0) scale.X = -scale.X; - if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; - } - } - - protected override SelectionBlueprint CreateBlueprintFor(SkinnableHUDComponent component) - => new SkinBlueprint(component); - - public class SkinBlueprint : SelectionBlueprint - { - /// - /// The which this applies to. - /// - public readonly SkinnableHUDComponent Component; - - private Container box; - private Drawable drawable => Component.Drawable; - - /// - /// Whether the blueprint should be shown even when the is not alive. - /// - protected virtual bool AlwaysShowWhenSelected => false; - - protected override bool ShouldBeAlive => (Component.IsAlive && Component.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); - - public SkinBlueprint(SkinnableHUDComponent component) - : base(component) - { - Component = component; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - InternalChildren = new Drawable[] - { - box = new Container - { - Colour = colours.Yellow, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - AlwaysPresent = true, - }, - } - }, - }; - } - - private Quad drawableQuad; - - public override Quad ScreenSpaceDrawQuad => drawableQuad; - - protected override void Update() - { - base.Update(); - - drawableQuad = drawable.ScreenSpaceDrawQuad; - var quad = ToLocalSpace(drawable.ScreenSpaceDrawQuad); - - box.Position = quad.TopLeft; - box.Size = quad.Size; - box.Rotation = Component.Rotation; - } - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); - - public override Vector2 ScreenSpaceSelectionPoint => Component.ToScreenSpace(Vector2.Zero); - - public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; - - public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Component.Parent.ToLocalSpace(screenSpacePosition) - Component.Position; - } - } - } + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs index 5b8d313400..2064f8589d 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs @@ -34,7 +34,10 @@ namespace osu.Game.Screens.Play.HUD SkinScale.BindValueChanged(scale => Drawable.Scale = new Vector2(scale.NewValue)); SkinPosition.BindValueChanged(position => Position = new Vector2(position.NewValue)); SkinRotation.BindValueChanged(rotation => Drawable.Rotation = rotation.NewValue); - SkinAnchor.BindValueChanged(anchor => Anchor = anchor.NewValue); + SkinAnchor.BindValueChanged(anchor => + { + Drawable.Anchor = anchor.NewValue; + }); } protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs new file mode 100644 index 0000000000..f9fffab579 --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -0,0 +1,86 @@ +// 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.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Skinning.Editor +{ + public class SkinBlueprint : SelectionBlueprint + { + /// + /// The which this applies to. + /// + public readonly SkinnableHUDComponent Component; + + private Container box; + private Drawable drawable => Component.Drawable; + + /// + /// Whether the blueprint should be shown even when the is not alive. + /// + protected virtual bool AlwaysShowWhenSelected => false; + + protected override bool ShouldBeAlive => (Component.IsAlive && Component.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); + + public SkinBlueprint(SkinnableHUDComponent component) + : base(component) + { + Component = component; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + box = new Container + { + Colour = colours.Yellow, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + AlwaysPresent = true, + }, + } + }, + }; + } + + private Quad drawableQuad; + + public override Quad ScreenSpaceDrawQuad => drawableQuad; + + protected override void Update() + { + base.Update(); + + drawableQuad = drawable.ScreenSpaceDrawQuad; + var quad = ToLocalSpace(drawable.ScreenSpaceDrawQuad); + + box.Position = quad.TopLeft; + box.Size = quad.Size; + box.Rotation = Component.Rotation; + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); + + public override Vector2 ScreenSpaceSelectionPoint => Component.ToScreenSpace(Vector2.Zero); + + public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; + + public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Component.Parent.ToLocalSpace(screenSpacePosition) - Component.Position; + } +} diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs new file mode 100644 index 0000000000..ddf3fd28a7 --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -0,0 +1,36 @@ +// 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.Graphics; +using osu.Framework.Testing; +using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Skinning.Editor +{ + public class SkinBlueprintContainer : BlueprintContainer + { + private readonly Drawable target; + + public SkinBlueprintContainer(Drawable target) + { + this.target = target; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + SkinnableHUDComponent[] components = target.ChildrenOfType().ToArray(); + + foreach (var c in components) AddBlueprintFor(c); + } + + protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); + + protected override SelectionBlueprint CreateBlueprintFor(SkinnableHUDComponent component) + => new SkinBlueprint(component); + } +} diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs new file mode 100644 index 0000000000..74d32bcb9f --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -0,0 +1,36 @@ +// 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.Graphics.Cursor; + +namespace osu.Game.Skinning.Editor +{ + public class SkinEditor : CompositeDrawable + { + private readonly Drawable target; + + public SkinEditor(Drawable target) + { + this.target = target; + + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + InternalChild = new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Children = new[] + { + target, + new SkinBlueprintContainer(target), + } + }; + } + } +} diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs new file mode 100644 index 0000000000..4f28d4e0d2 --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.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; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Skinning.Editor +{ + public class SkinSelectionHandler : SelectionHandler + { + protected override void DeleteItems(IEnumerable items) + { + foreach (var i in items) + i.Drawable.Expire(); + } + + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) + { + yield return new OsuMenuItem("Anchor") + { + Items = createAnchorItems().ToArray() + }; + + foreach (var item in base.GetContextMenuItemsForSelection(selection)) + yield return item; + + IEnumerable createAnchorItems() + { + var displayableAnchors = new[] + { + Anchor.TopLeft, + Anchor.TopCentre, + Anchor.TopRight, + Anchor.CentreLeft, + Anchor.Centre, + Anchor.CentreRight, + Anchor.BottomLeft, + Anchor.BottomCentre, + Anchor.BottomRight, + }; + + return displayableAnchors.Select(a => + { + var countExisting = selection.Count(b => b.Item.SkinAnchor.Value == a); + var countTotal = selection.Count(); + + TernaryState state; + + if (countExisting == countTotal) + state = TernaryState.True; + else if (countExisting > 0) + state = TernaryState.Indeterminate; + else + state = TernaryState.False; + + return new AnchorMenuItem(a, selection, _ => applyAnchor(a)) + { + State = { Value = state } + }; + }); + } + } + + private void applyAnchor(Anchor anchor) + { + foreach (var item in SelectedItems) + item.SkinAnchor.Value = anchor; + } + + protected override void OnSelectionChanged() + { + base.OnSelectionChanged(); + + SelectionBox.CanRotate = true; + SelectionBox.CanScaleX = true; + SelectionBox.CanScaleY = true; + SelectionBox.CanReverse = false; + } + + public override bool HandleRotation(float angle) + { + foreach (var c in SelectedBlueprints) + c.Item.SkinRotation.Value += angle; + + return base.HandleRotation(angle); + } + + public override bool HandleScale(Vector2 scale, Anchor anchor) + { + adjustScaleFromAnchor(ref scale, anchor); + + foreach (var c in SelectedBlueprints) + c.Item.SkinScale.Value += scale.X * 0.01f; + + return true; + } + + public override bool HandleMovement(MoveSelectionEvent moveEvent) + { + foreach (var c in SelectedBlueprints) + c.Item.SkinPosition.Value += moveEvent.InstantDelta.X; + return true; + } + + private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) + { + // cancel out scale in axes we don't care about (based on which drag handle was used). + if ((reference & Anchor.x1) > 0) scale.X = 0; + if ((reference & Anchor.y1) > 0) scale.Y = 0; + + // reverse the scale direction if dragging from top or left. + if ((reference & Anchor.x0) > 0) scale.X = -scale.X; + if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; + } + + public class AnchorMenuItem : TernaryStateMenuItem + { + public AnchorMenuItem(Anchor anchor, IEnumerable> selection, Action action) + : base(anchor.ToString(), getNextState, MenuItemType.Standard, action) + { + } + + private void updateState(TernaryState obj) + { + throw new NotImplementedException(); + } + + private static TernaryState getNextState(TernaryState state) => TernaryState.True; + } + } +} From 59339aa4fd3e46485c83c0a1c73eec18e45bddf9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 15:28:32 +0900 Subject: [PATCH 309/400] Add support for x/y position and scale back in --- .../Screens/Play/HUD/SkinnableHUDComponent.cs | 28 +++++++++++++------ .../Skinning/Editor/SkinSelectionHandler.cs | 11 ++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs index 2064f8589d..533573efd2 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs @@ -16,11 +16,17 @@ namespace osu.Game.Screens.Play.HUD /// public abstract class SkinnableHUDComponent : SkinnableDrawable { - [SettingSource("Scale", "The scale at which this component should be displayed.")] - public BindableNumber SkinScale { get; } = new BindableFloat(1); + [SettingSource("ScaleX", "The horizontal scale at which this component should be displayed.")] + public BindableNumber SkinScaleX { get; } = new BindableFloat(1); - [SettingSource("Position", "The position at which this component should be displayed.")] - public BindableNumber SkinPosition { get; } = new BindableFloat(); + [SettingSource("ScaleY", "The vertical scale at which this component should be displayed.")] + public BindableNumber SkinScaleY { get; } = new BindableFloat(1); + + [SettingSource("PositionX", "The horizontal position at which this component should be displayed.")] + public BindableNumber SkinPositionX { get; } = new BindableFloat(); + + [SettingSource("PositionY", "The vertical position at which this component should be displayed.")] + public BindableNumber SkinPositionY { get; } = new BindableFloat(); [SettingSource("Rotation", "The rotation at which this component should be displayed.")] public BindableNumber SkinRotation { get; } = new BindableFloat(); @@ -31,8 +37,12 @@ namespace osu.Game.Screens.Play.HUD protected SkinnableHUDComponent(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(component, defaultImplementation, allowFallback, confineMode) { - SkinScale.BindValueChanged(scale => Drawable.Scale = new Vector2(scale.NewValue)); - SkinPosition.BindValueChanged(position => Position = new Vector2(position.NewValue)); + SkinScaleX.BindValueChanged(x => Drawable.Scale = new Vector2(x.NewValue, Drawable.Scale.Y)); + SkinScaleY.BindValueChanged(y => Drawable.Scale = new Vector2(Drawable.Scale.X, y.NewValue)); + + SkinPositionX.BindValueChanged(x => Position = new Vector2(x.NewValue, Position.Y)); + SkinPositionY.BindValueChanged(y => Position = new Vector2(Position.X, y.NewValue)); + SkinRotation.BindValueChanged(rotation => Drawable.Rotation = rotation.NewValue); SkinAnchor.BindValueChanged(anchor => { @@ -42,8 +52,10 @@ namespace osu.Game.Screens.Play.HUD protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) { - SkinScale.Value = Drawable.Scale.X; - SkinPosition.Value = Position.X; + SkinScaleX.Value = Drawable.Scale.X; + SkinScaleY.Value = Drawable.Scale.Y; + SkinPositionX.Value = Position.X; + SkinPositionY.Value = Position.Y; SkinRotation.Value = Drawable.Rotation; SkinAnchor.Value = Anchor; return base.OnInvalidate(invalidation, source); diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 4f28d4e0d2..55f36c73d6 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -98,7 +98,10 @@ namespace osu.Game.Skinning.Editor adjustScaleFromAnchor(ref scale, anchor); foreach (var c in SelectedBlueprints) - c.Item.SkinScale.Value += scale.X * 0.01f; + { + c.Item.SkinScaleX.Value += scale.X * 0.01f; + c.Item.SkinScaleY.Value += scale.Y * 0.01f; + } return true; } @@ -106,7 +109,11 @@ namespace osu.Game.Skinning.Editor public override bool HandleMovement(MoveSelectionEvent moveEvent) { foreach (var c in SelectedBlueprints) - c.Item.SkinPosition.Value += moveEvent.InstantDelta.X; + { + c.Item.SkinPositionX.Value += moveEvent.InstantDelta.X; + c.Item.SkinPositionY.Value += moveEvent.InstantDelta.Y; + } + return true; } From 2540d6029c4d804dd2e0378c35b58319349c8ebb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 17:47:28 +0900 Subject: [PATCH 310/400] Use AutoSize for SkinnableHudComponents --- .../Screens/Play/HUD/SkinnableHUDComponent.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs index 533573efd2..f5333c940f 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs @@ -37,26 +37,26 @@ namespace osu.Game.Screens.Play.HUD protected SkinnableHUDComponent(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : base(component, defaultImplementation, allowFallback, confineMode) { - SkinScaleX.BindValueChanged(x => Drawable.Scale = new Vector2(x.NewValue, Drawable.Scale.Y)); - SkinScaleY.BindValueChanged(y => Drawable.Scale = new Vector2(Drawable.Scale.X, y.NewValue)); + SkinScaleX.BindValueChanged(x => Scale = new Vector2(x.NewValue, Scale.Y)); + SkinScaleY.BindValueChanged(y => Scale = new Vector2(Scale.X, y.NewValue)); SkinPositionX.BindValueChanged(x => Position = new Vector2(x.NewValue, Position.Y)); SkinPositionY.BindValueChanged(y => Position = new Vector2(Position.X, y.NewValue)); - SkinRotation.BindValueChanged(rotation => Drawable.Rotation = rotation.NewValue); - SkinAnchor.BindValueChanged(anchor => - { - Drawable.Anchor = anchor.NewValue; - }); + SkinRotation.BindValueChanged(rotation => Rotation = rotation.NewValue); + SkinAnchor.BindValueChanged(anchor => { Anchor = anchor.NewValue; }); + + RelativeSizeAxes = Axes.None; + AutoSizeAxes = Axes.Both; } protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) { - SkinScaleX.Value = Drawable.Scale.X; - SkinScaleY.Value = Drawable.Scale.Y; + SkinScaleX.Value = Scale.X; + SkinScaleY.Value = Scale.Y; SkinPositionX.Value = Position.X; SkinPositionY.Value = Position.Y; - SkinRotation.Value = Drawable.Rotation; + SkinRotation.Value = Rotation; SkinAnchor.Value = Anchor; return base.OnInvalidate(invalidation, source); } From defa350aa71cf07ffcee310fbfbde9dbc1c1c187 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 18:06:59 +0900 Subject: [PATCH 311/400] Set defaults on SkinnableHUDComponent to cancel out relative size default Specifying locally on each HUD component looks to make more sense. --- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 26 ------------------- .../Play/HUD/SkinnableAccuracyCounter.cs | 2 ++ .../Screens/Play/HUD/SkinnableComboCounter.cs | 2 ++ .../Screens/Play/HUD/SkinnableHUDComponent.cs | 6 ++++- .../Play/HUD/SkinnableHealthDisplay.cs | 3 +++ .../Screens/Play/HUD/SkinnableScoreCounter.cs | 2 ++ 6 files changed, 14 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 4f8a78368e..5c3d3c03bf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -36,32 +36,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuConfigManager config { get; set; } - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(new Container - { - RelativeSizeAxes = Axes.Both, - Width = 0.3f, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - Alpha = 0.9f, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = createSkinSourceComponents(), - }, - } - }); - } - [Test] public void TestComboCounterIncrementing() { diff --git a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs index ebb3bcd148..33132adf23 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD @@ -14,6 +15,7 @@ namespace osu.Game.Screens.Play.HUD : base(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter), _ => new DefaultAccuracyCounter()) { CentreComponent = false; + AutoSizeAxes = Axes.Both; } private IAccuracyCounter skinnedCounter; diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index 629bb467bc..8785eccfc3 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD @@ -14,6 +15,7 @@ namespace osu.Game.Screens.Play.HUD : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), skinComponent => new DefaultComboCounter()) { CentreComponent = false; + AutoSizeAxes = Axes.Both; } private IComboCounter skinnedCounter; diff --git a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs index f5333c940f..7d23611718 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs @@ -46,8 +46,11 @@ namespace osu.Game.Screens.Play.HUD SkinRotation.BindValueChanged(rotation => Rotation = rotation.NewValue); SkinAnchor.BindValueChanged(anchor => { Anchor = anchor.NewValue; }); + // reset everything and require each component to specify what they want, + // as if they were just drawables. maybe we want to change SkinnableDrawable to not default + // to RelativeSizeAxes.Both... RelativeSizeAxes = Axes.None; - AutoSizeAxes = Axes.Both; + AutoSizeAxes = Axes.None; } protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) @@ -58,6 +61,7 @@ namespace osu.Game.Screens.Play.HUD SkinPositionY.Value = Position.Y; SkinRotation.Value = Rotation; SkinAnchor.Value = Anchor; + return base.OnInvalidate(invalidation, source); } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs index f8a8696dae..ceac147b63 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -35,6 +36,8 @@ namespace osu.Game.Screens.Play.HUD : base(new HUDSkinComponent(HUDSkinComponents.HealthDisplay), _ => new DefaultHealthDisplay()) { CentreComponent = false; + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; } private IHealthDisplay skinnedCounter; diff --git a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs index 9165b2c7f0..c47baf95ff 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -22,6 +23,7 @@ namespace osu.Game.Screens.Play.HUD : base(new HUDSkinComponent(HUDSkinComponents.ScoreCounter), _ => new DefaultScoreCounter()) { CentreComponent = false; + AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] From fd587a82ffe2f73ac5302b528222b0d8f42ad78b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 18:34:34 +0900 Subject: [PATCH 312/400] Replace abstract class with interface, attached to the actual components (not skinnable wrapper) --- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 27 -------- .../Play/HUD/DefaultAccuracyCounter.cs | 2 +- .../Screens/Play/HUD/DefaultComboCounter.cs | 2 +- .../Screens/Play/HUD/DefaultHealthDisplay.cs | 2 +- .../Screens/Play/HUD/DefaultScoreCounter.cs | 2 +- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 2 +- .../Screens/Play/HUD/ISkinnableComponent.cs | 14 ++++ .../Screens/Play/HUD/LegacyComboCounter.cs | 2 +- .../Play/HUD/SkinnableAccuracyCounter.cs | 4 +- .../Screens/Play/HUD/SkinnableComboCounter.cs | 4 +- .../Screens/Play/HUD/SkinnableHUDComponent.cs | 68 ------------------- .../Play/HUD/SkinnableHealthDisplay.cs | 5 +- .../Screens/Play/HUD/SkinnableScoreCounter.cs | 4 +- osu.Game/Skinning/Editor/SkinBlueprint.cs | 17 ++--- .../Skinning/Editor/SkinBlueprintContainer.cs | 8 +-- .../Skinning/Editor/SkinSelectionHandler.cs | 26 +++---- osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- osu.Game/Skinning/LegacyHealthDisplay.cs | 2 +- osu.Game/Skinning/LegacyScoreCounter.cs | 3 +- 19 files changed, 52 insertions(+), 144 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/ISkinnableComponent.cs delete mode 100644 osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 5c3d3c03bf..fec1610160 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -9,16 +9,12 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Configuration; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD; -using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -78,29 +74,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent); } - private IReadOnlyList createSkinSourceComponents() - { - Drawable[] hudComponents = typeof(SkinnableHUDComponent).Assembly - .GetTypes() - .Where(t => typeof(SkinnableHUDComponent).IsAssignableFrom(t)) - .Where(t => !t.IsAbstract) - .Select(t => Activator.CreateInstance(t) as Drawable) - .ToArray(); - - List drawables = new List(); - - foreach (var component in hudComponents) - { - drawables.Add(new OsuSpriteText - { - Text = component.GetType().Name - }); - drawables.AddRange(component.CreateSettingsControls()); - } - - return drawables; - } - private void createNew(Action action = null) { AddStep("create overlay", () => diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index d5d8ec570a..b8a43708b4 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -9,7 +9,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { - public class DefaultAccuracyCounter : PercentageCounter, IAccuracyCounter + public class DefaultAccuracyCounter : PercentageCounter, IAccuracyCounter, ISkinnableComponent { private readonly Vector2 offset = new Vector2(-20, 5); diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 63e7a88550..959766ecd1 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { - public class DefaultComboCounter : RollingCounter, IComboCounter + public class DefaultComboCounter : RollingCounter, IComboCounter, ISkinnableComponent { private readonly Vector2 offset = new Vector2(20, 5); diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index b550b469e9..e3cd71691d 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -16,7 +16,7 @@ using osu.Framework.Utils; namespace osu.Game.Screens.Play.HUD { - public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour + public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour, ISkinnableComponent { /// /// The base opacity of the glow. diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index 1dcfe2e067..dde5c18b38 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Play.HUD { - public class DefaultScoreCounter : ScoreCounter + public class DefaultScoreCounter : ScoreCounter, ISkinnableComponent { public DefaultScoreCounter() : base(6) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 89f135de7f..3b24c8cc9e 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -18,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { - public class BarHitErrorMeter : HitErrorMeter + public class BarHitErrorMeter : HitErrorMeter, ISkinnableComponent { private readonly Anchor alignment; diff --git a/osu.Game/Screens/Play/HUD/ISkinnableComponent.cs b/osu.Game/Screens/Play/HUD/ISkinnableComponent.cs new file mode 100644 index 0000000000..6d4558443f --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ISkinnableComponent.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications. + /// + public interface ISkinnableComponent : IDrawable + { + } +} diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index b4604c0d01..4ae722e44c 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.HUD /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public class LegacyComboCounter : CompositeDrawable, IComboCounter + public class LegacyComboCounter : CompositeDrawable, IComboCounter, ISkinnableComponent { public Bindable Current { get; } = new BindableInt { MinValue = 0, }; diff --git a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs index 33132adf23..76c9c30813 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs @@ -2,12 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableAccuracyCounter : SkinnableHUDComponent, IAccuracyCounter + public class SkinnableAccuracyCounter : SkinnableDrawable, IAccuracyCounter { public Bindable Current { get; } = new Bindable(); @@ -15,7 +14,6 @@ namespace osu.Game.Screens.Play.HUD : base(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter), _ => new DefaultAccuracyCounter()) { CentreComponent = false; - AutoSizeAxes = Axes.Both; } private IAccuracyCounter skinnedCounter; diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index 8785eccfc3..c04c50141a 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -2,12 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableComboCounter : SkinnableHUDComponent, IComboCounter + public class SkinnableComboCounter : SkinnableDrawable, IComboCounter { public Bindable Current { get; } = new Bindable(); @@ -15,7 +14,6 @@ namespace osu.Game.Screens.Play.HUD : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), skinComponent => new DefaultComboCounter()) { CentreComponent = false; - AutoSizeAxes = Axes.Both; } private IComboCounter skinnedCounter; diff --git a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs b/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs deleted file mode 100644 index 7d23611718..0000000000 --- a/osu.Game/Screens/Play/HUD/SkinnableHUDComponent.cs +++ /dev/null @@ -1,68 +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.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Layout; -using osu.Game.Configuration; -using osu.Game.Skinning; -using osuTK; - -namespace osu.Game.Screens.Play.HUD -{ - /// - /// A skinnable HUD component which can be scaled and repositioned at a skinner/user's will. - /// - public abstract class SkinnableHUDComponent : SkinnableDrawable - { - [SettingSource("ScaleX", "The horizontal scale at which this component should be displayed.")] - public BindableNumber SkinScaleX { get; } = new BindableFloat(1); - - [SettingSource("ScaleY", "The vertical scale at which this component should be displayed.")] - public BindableNumber SkinScaleY { get; } = new BindableFloat(1); - - [SettingSource("PositionX", "The horizontal position at which this component should be displayed.")] - public BindableNumber SkinPositionX { get; } = new BindableFloat(); - - [SettingSource("PositionY", "The vertical position at which this component should be displayed.")] - public BindableNumber SkinPositionY { get; } = new BindableFloat(); - - [SettingSource("Rotation", "The rotation at which this component should be displayed.")] - public BindableNumber SkinRotation { get; } = new BindableFloat(); - - [SettingSource("Anchor", "The screen edge this component should align to.")] - public Bindable SkinAnchor { get; } = new Bindable(); - - protected SkinnableHUDComponent(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) - : base(component, defaultImplementation, allowFallback, confineMode) - { - SkinScaleX.BindValueChanged(x => Scale = new Vector2(x.NewValue, Scale.Y)); - SkinScaleY.BindValueChanged(y => Scale = new Vector2(Scale.X, y.NewValue)); - - SkinPositionX.BindValueChanged(x => Position = new Vector2(x.NewValue, Position.Y)); - SkinPositionY.BindValueChanged(y => Position = new Vector2(Position.X, y.NewValue)); - - SkinRotation.BindValueChanged(rotation => Rotation = rotation.NewValue); - SkinAnchor.BindValueChanged(anchor => { Anchor = anchor.NewValue; }); - - // reset everything and require each component to specify what they want, - // as if they were just drawables. maybe we want to change SkinnableDrawable to not default - // to RelativeSizeAxes.Both... - RelativeSizeAxes = Axes.None; - AutoSizeAxes = Axes.None; - } - - protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) - { - SkinScaleX.Value = Scale.X; - SkinScaleY.Value = Scale.Y; - SkinPositionX.Value = Position.X; - SkinPositionY.Value = Position.Y; - SkinRotation.Value = Rotation; - SkinAnchor.Value = Anchor; - - return base.OnInvalidate(invalidation, source); - } - } -} diff --git a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs index ceac147b63..1f91f5e50f 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs @@ -3,14 +3,13 @@ using System; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableHealthDisplay : SkinnableHUDComponent, IHealthDisplay + public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay { public Bindable Current { get; } = new BindableDouble(1) { @@ -36,8 +35,6 @@ namespace osu.Game.Screens.Play.HUD : base(new HUDSkinComponent(HUDSkinComponents.HealthDisplay), _ => new DefaultHealthDisplay()) { CentreComponent = false; - AutoSizeAxes = Axes.Y; - RelativeSizeAxes = Axes.X; } private IHealthDisplay skinnedCounter; diff --git a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs index c47baf95ff..b46f5684b1 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs @@ -4,14 +4,13 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableScoreCounter : SkinnableHUDComponent, IScoreCounter + public class SkinnableScoreCounter : SkinnableDrawable, IScoreCounter { public Bindable Current { get; } = new Bindable(); @@ -23,7 +22,6 @@ namespace osu.Game.Screens.Play.HUD : base(new HUDSkinComponent(HUDSkinComponents.ScoreCounter), _ => new DefaultScoreCounter()) { CentreComponent = false; - AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index f9fffab579..674153cb26 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -15,24 +15,25 @@ using osuTK; namespace osu.Game.Skinning.Editor { - public class SkinBlueprint : SelectionBlueprint + public class SkinBlueprint : SelectionBlueprint { /// /// The which this applies to. /// - public readonly SkinnableHUDComponent Component; + public readonly ISkinnableComponent Component; private Container box; - private Drawable drawable => Component.Drawable; + + private Drawable drawable => (Drawable)Component; /// /// Whether the blueprint should be shown even when the is not alive. /// protected virtual bool AlwaysShowWhenSelected => false; - protected override bool ShouldBeAlive => (Component.IsAlive && Component.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); + protected override bool ShouldBeAlive => (drawable.IsAlive && Component.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); - public SkinBlueprint(SkinnableHUDComponent component) + public SkinBlueprint(ISkinnableComponent component) : base(component) { Component = component; @@ -72,15 +73,15 @@ namespace osu.Game.Skinning.Editor box.Position = quad.TopLeft; box.Size = quad.Size; - box.Rotation = Component.Rotation; + box.Rotation = drawable.Rotation; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); - public override Vector2 ScreenSpaceSelectionPoint => Component.ToScreenSpace(Vector2.Zero); + public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(Vector2.Zero); public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; - public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Component.Parent.ToLocalSpace(screenSpacePosition) - Component.Position; + public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Component.Parent.ToLocalSpace(screenSpacePosition) - drawable.Position; } } diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index ddf3fd28a7..f997e84355 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning.Editor { - public class SkinBlueprintContainer : BlueprintContainer + public class SkinBlueprintContainer : BlueprintContainer { private readonly Drawable target; @@ -23,14 +23,14 @@ namespace osu.Game.Skinning.Editor { base.LoadComplete(); - SkinnableHUDComponent[] components = target.ChildrenOfType().ToArray(); + ISkinnableComponent[] components = target.ChildrenOfType().ToArray(); foreach (var c in components) AddBlueprintFor(c); } - protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); - protected override SelectionBlueprint CreateBlueprintFor(SkinnableHUDComponent component) + protected override SelectionBlueprint CreateBlueprintFor(ISkinnableComponent component) => new SkinBlueprint(component); } } diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 55f36c73d6..38404efd0c 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -14,15 +14,15 @@ using osuTK; namespace osu.Game.Skinning.Editor { - public class SkinSelectionHandler : SelectionHandler + public class SkinSelectionHandler : SelectionHandler { - protected override void DeleteItems(IEnumerable items) + protected override void DeleteItems(IEnumerable items) { foreach (var i in items) - i.Drawable.Expire(); + i.Hide(); } - protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { yield return new OsuMenuItem("Anchor") { @@ -49,7 +49,7 @@ namespace osu.Game.Skinning.Editor return displayableAnchors.Select(a => { - var countExisting = selection.Count(b => b.Item.SkinAnchor.Value == a); + var countExisting = selection.Count(b => ((Drawable)b.Item).Anchor == a); var countTotal = selection.Count(); TernaryState state; @@ -72,7 +72,7 @@ namespace osu.Game.Skinning.Editor private void applyAnchor(Anchor anchor) { foreach (var item in SelectedItems) - item.SkinAnchor.Value = anchor; + ((Drawable)item).Anchor = anchor; } protected override void OnSelectionChanged() @@ -88,7 +88,7 @@ namespace osu.Game.Skinning.Editor public override bool HandleRotation(float angle) { foreach (var c in SelectedBlueprints) - c.Item.SkinRotation.Value += angle; + ((Drawable)c.Item).Rotation += angle; return base.HandleRotation(angle); } @@ -98,20 +98,16 @@ namespace osu.Game.Skinning.Editor adjustScaleFromAnchor(ref scale, anchor); foreach (var c in SelectedBlueprints) - { - c.Item.SkinScaleX.Value += scale.X * 0.01f; - c.Item.SkinScaleY.Value += scale.Y * 0.01f; - } + ((Drawable)c.Item).Scale += scale * 0.01f; return true; } - public override bool HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { foreach (var c in SelectedBlueprints) { - c.Item.SkinPositionX.Value += moveEvent.InstantDelta.X; - c.Item.SkinPositionY.Value += moveEvent.InstantDelta.Y; + ((Drawable)c.Item).Position += moveEvent.InstantDelta; } return true; @@ -130,7 +126,7 @@ namespace osu.Game.Skinning.Editor public class AnchorMenuItem : TernaryStateMenuItem { - public AnchorMenuItem(Anchor anchor, IEnumerable> selection, Action action) + public AnchorMenuItem(Anchor anchor, IEnumerable> selection, Action action) : base(anchor.ToString(), getNextState, MenuItemType.Standard, action) { } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 7d6f1dc916..c592e06924 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Skinning { - public class LegacyAccuracyCounter : PercentageCounter, IAccuracyCounter + public class LegacyAccuracyCounter : PercentageCounter, IAccuracyCounter, ISkinnableComponent { private readonly ISkin skin; diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 2921d46467..2e29abf453 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Skinning { - public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay + public class LegacyHealthDisplay : CompositeDrawable, IHealthDisplay, ISkinnableComponent { private const double epic_cutoff = 0.5; diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 1d330ef495..cae8044242 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -5,11 +5,12 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Skinning { - public class LegacyScoreCounter : ScoreCounter + public class LegacyScoreCounter : ScoreCounter, ISkinnableComponent { private readonly ISkin skin; From 4f9e1e4945d002ea20a35855bd2bcdd160aa50eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 18:59:55 +0900 Subject: [PATCH 313/400] Check for new components every one second to handle late loaders --- osu.Game/Skinning/Editor/SkinBlueprintContainer.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index f997e84355..f2bc8ecddf 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -23,9 +23,14 @@ namespace osu.Game.Skinning.Editor { base.LoadComplete(); - ISkinnableComponent[] components = target.ChildrenOfType().ToArray(); + checkForComponents(); + } - foreach (var c in components) AddBlueprintFor(c); + private void checkForComponents() + { + foreach (var c in target.ChildrenOfType().ToArray()) AddBlueprintFor(c); + + Scheduler.AddDelayed(checkForComponents, 1000); } protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); From 74c6fdc4b8e52611b8e51645e19f27b9e042e5e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 19:00:16 +0900 Subject: [PATCH 314/400] Add `DrawableRuleset` to the skin editor test to get a hit error meter to display --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index e46cd70667..eaa0c29616 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -2,10 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning.Editor; using osuTK.Input; @@ -21,13 +26,32 @@ namespace osu.Game.Tests.Visual.Gameplay { SetContents(() => { - var hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + var ruleset = new OsuRuleset(); + var working = CreateWorkingBeatmap(ruleset.RulesetInfo); + var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo); + + ScoreProcessor scoreProcessor = new ScoreProcessor(); + + var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap); + + var hudOverlay = new HUDOverlay(scoreProcessor, null, drawableRuleset, Array.Empty()); // Add any key just to display the key counter visually. hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); hudOverlay.ComboCounter.Current.Value = 1; - return new SkinEditor(hudOverlay); + // Apply a miss judgement + scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Good }); + + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + drawableRuleset, + new SkinEditor(hudOverlay), + } + }; }); }); } From de73ac7ceccfa3d518baa6ec93190efda3ea2d24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 13:53:01 +0900 Subject: [PATCH 315/400] Allow skin editor to be invoked from any context This is kind of how I see things working going forward, where the editor can be applied to anything in the game which supports it (ie. a results screen, gameplay screen, etc.) and it will immediately allow changing the interface. This adds a test scene which shows this working with gameplay. --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 59 +++++------------ .../TestSceneSkinEditorMultipleSkins.cs | 61 +++++++++++++++++ osu.Game/Skinning/Editor/SkinEditor.cs | 66 +++++++++++++++++-- 3 files changed, 138 insertions(+), 48 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index eaa0c29616..0c2c6ed454 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -1,61 +1,36 @@ // 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 NUnit.Framework; using osu.Framework.Testing; using osu.Game.Rulesets; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play; using osu.Game.Skinning.Editor; -using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneSkinEditor : SkinnableTestScene + public class TestSceneSkinEditor : PlayerTestScene { + private SkinEditor skinEditor; + [SetUpSteps] - public void SetUpSteps() + public override void SetUpSteps() { - AddStep("create editor overlay", () => + base.SetUpSteps(); + + AddStep("add editor overlay", () => { - SetContents(() => - { - var ruleset = new OsuRuleset(); - var working = CreateWorkingBeatmap(ruleset.RulesetInfo); - var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo); - - ScoreProcessor scoreProcessor = new ScoreProcessor(); - - var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap); - - var hudOverlay = new HUDOverlay(scoreProcessor, null, drawableRuleset, Array.Empty()); - - // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); - hudOverlay.ComboCounter.Current.Value = 1; - - // Apply a miss judgement - scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Good }); - - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - drawableRuleset, - new SkinEditor(hudOverlay), - } - }; - }); + skinEditor?.Expire(); + LoadComponentAsync(skinEditor = new SkinEditor(Player), Add); }); } - protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + [Test] + public void TestToggleEditor() + { + AddToggleStep("toggle editor visibility", visible => skinEditor.ToggleVisibility()); + } + + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs new file mode 100644 index 0000000000..086bcb19c3 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -0,0 +1,61 @@ +// 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 osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Skinning.Editor; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinEditorMultipleSkins : SkinnableTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create editor overlay", () => + { + SetContents(() => + { + var ruleset = new OsuRuleset(); + var working = CreateWorkingBeatmap(ruleset.RulesetInfo); + var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo); + + ScoreProcessor scoreProcessor = new ScoreProcessor(); + + var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap); + + var hudOverlay = new HUDOverlay(scoreProcessor, null, drawableRuleset, Array.Empty()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + hudOverlay.ComboCounter.Current.Value = 1; + + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + drawableRuleset, + hudOverlay, + new SkinEditor(hudOverlay), + } + }; + }); + }); + } + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + } +} diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 74d32bcb9f..4f81fad4ba 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -1,15 +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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.Cursor; namespace osu.Game.Skinning.Editor { - public class SkinEditor : CompositeDrawable + public class SkinEditor : VisibilityContainer { private readonly Drawable target; + private Container border; + + protected override bool StartHidden => true; public SkinEditor(Drawable target) { @@ -18,19 +24,67 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.Both; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - base.LoadComplete(); - InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - Children = new[] + Children = new Drawable[] { - target, + border = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = colours.Yellow, + BorderThickness = 5, + CornerRadius = 5, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both, + }, + } + }, new SkinBlueprintContainer(target), } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + Show(); + } + + private const double transition_duration = 500; + private const float visible_target_scale = 0.8f; + + protected override void PopIn() + { + if (IsLoaded) + { + target.ScaleTo(visible_target_scale, transition_duration, Easing.OutQuint); + border.ScaleTo(visible_target_scale, transition_duration, Easing.OutQuint); + } + + this.FadeIn(transition_duration); + } + + protected override void PopOut() + { + if (IsLoaded) + { + target.ScaleTo(1, transition_duration, Easing.OutQuint); + border.ScaleTo(1, transition_duration, Easing.OutQuint); + } + + this.FadeOut(transition_duration, Easing.OutQuint); + } } } From 3c84b0d8c624db113873e8b53a4f1f6cf491ba0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 14:07:15 +0900 Subject: [PATCH 316/400] Fix selection screen point being wrong since recent refactors --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index 674153cb26..2cee94b2c6 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -78,10 +78,10 @@ namespace osu.Game.Skinning.Editor public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); - public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(Vector2.Zero); + public override Vector2 ScreenSpaceSelectionPoint => drawable.Parent.ToScreenSpace(drawable.DrawPosition); public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; - public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Component.Parent.ToLocalSpace(screenSpacePosition) - drawable.Position; + public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Component.Parent.ToLocalSpace(screenSpacePosition) - drawable.DrawPosition; } } From 434e63d6297c9e6db9ed3a70562faa519c67ac8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 14:14:04 +0900 Subject: [PATCH 317/400] Add skin customisation support to song progress display --- osu.Game/Screens/Play/SongProgress.cs | 57 +++++++++++++++++---------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 6c7cb9376c..db81633aea 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -14,6 +14,7 @@ using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Screens.Play { @@ -71,30 +72,38 @@ namespace osu.Game.Screens.Play public SongProgress() { - Masking = true; - Children = new Drawable[] { - info = new SongProgressInfo + new SongProgressDisplay { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = info_height, - }, - graph = new SongProgressGraph - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Height = graph_height, - Margin = new MarginPadding { Bottom = bottom_bar_height }, - }, - bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - OnSeek = time => RequestSeek?.Invoke(time), + Masking = true, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new Drawable[] + { + info = new SongProgressInfo + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = info_height, + }, + graph = new SongProgressGraph + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Height = graph_height, + Margin = new MarginPadding { Bottom = bottom_bar_height }, + }, + bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + OnSeek = time => RequestSeek?.Invoke(time), + }, + } }, }; } @@ -175,5 +184,11 @@ namespace osu.Game.Screens.Play float finalMargin = bottom_bar_height + (AllowSeeking.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0); info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In); } + + public class SongProgressDisplay : Container, ISkinnableComponent + { + // TODO: move actual implementation into this. + // exists for skin customisation purposes. + } } } From 1516e2ffef7228cff1f7d3c70ba7224dd9345a47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 15:29:25 +0900 Subject: [PATCH 318/400] Update blueprint implementation in line with #12625. --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 4 +--- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index 2cee94b2c6..491a403325 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -78,10 +78,8 @@ namespace osu.Game.Skinning.Editor public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); - public override Vector2 ScreenSpaceSelectionPoint => drawable.Parent.ToScreenSpace(drawable.DrawPosition); + public override Vector2 ScreenSpaceSelectionPoint => drawable.ScreenSpaceDrawQuad.Centre; public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; - - public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Component.Parent.ToLocalSpace(screenSpacePosition) - drawable.DrawPosition; } } diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 38404efd0c..466136f0a8 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; +using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components; @@ -107,7 +108,8 @@ namespace osu.Game.Skinning.Editor { foreach (var c in SelectedBlueprints) { - ((Drawable)c.Item).Position += moveEvent.InstantDelta; + Drawable drawable = (Drawable)c.Item; + drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); } return true; From b460181f15ea13569fdce9a557cda060b6775634 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 16:16:52 +0900 Subject: [PATCH 319/400] Add note about rotation not working as expected --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 466136f0a8..17b459a916 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -88,6 +88,7 @@ namespace osu.Game.Skinning.Editor public override bool HandleRotation(float angle) { + // TODO: this doesn't correctly account for origin/anchor specs being different in a multi-selection. foreach (var c in SelectedBlueprints) ((Drawable)c.Item).Rotation += angle; From 141d3af302e1443af684b8658e8eca180d9e746a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 17:19:47 +0900 Subject: [PATCH 320/400] Add the ability to temporarily disable user scaling on a `ScalingContainer` --- .../Graphics/Containers/ScalingContainer.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 8f07c3a656..b691e372c5 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -36,6 +36,24 @@ namespace osu.Game.Graphics.Containers private BackgroundScreenStack backgroundStack; + private bool allowScaling = true; + + /// + /// Whether user scaling preferences should be applied. Enabled by default. + /// + public bool AllowScaling + { + get => allowScaling; + set + { + if (value == allowScaling) + return; + + allowScaling = value; + updateSize(); + } + } + /// /// Create a new instance. /// @@ -139,7 +157,7 @@ namespace osu.Game.Graphics.Containers backgroundStack?.FadeOut(fade_time); } - bool scaling = targetMode == null || scalingMode.Value == targetMode; + bool scaling = AllowScaling && (targetMode == null || scalingMode.Value == targetMode); var targetSize = scaling ? new Vector2(sizeX.Value, sizeY.Value) : Vector2.One; var targetPosition = scaling ? new Vector2(posX.Value, posY.Value) * (Vector2.One - targetSize) : Vector2.Zero; From b93604395673af6dc74631f6601a241d06aac53f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 17:20:22 +0900 Subject: [PATCH 321/400] Add the skin editor to the game --- .../Input/Bindings/GlobalActionContainer.cs | 6 +- osu.Game/OsuGame.cs | 8 ++ osu.Game/Skinning/Editor/SkinEditor.cs | 50 +++-------- .../Skinning/Editor/SkinEditorContainer.cs | 87 +++++++++++++++++++ 4 files changed, 110 insertions(+), 41 deletions(-) create mode 100644 osu.Game/Skinning/Editor/SkinEditorContainer.cs diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 6717de5658..ce945f3bf8 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -48,6 +48,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleBeatmapListing), new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications), + new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.ToggleSkinEditor), new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back), @@ -258,6 +259,9 @@ namespace osu.Game.Input.Bindings EditorNudgeLeft, [Description("Nudge selection right")] - EditorNudgeRight + EditorNudgeRight, + + [Description("Toggle skin editor")] + ToggleSkinEditor, } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 28f32ba455..67e0e6a81c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -51,6 +51,7 @@ using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; using osu.Game.Database; using osu.Game.IO; +using osu.Game.Skinning.Editor; namespace osu.Game { @@ -597,6 +598,8 @@ namespace osu.Game screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) { RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Children = new Drawable[] { receptor = new BackButton.Receptor(), @@ -616,6 +619,7 @@ namespace osu.Game logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, + skinEditor = new SkinEditorContainer(screenContainer), overlayContent = new Container { RelativeSizeAxes = Axes.Both }, rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, @@ -942,6 +946,8 @@ namespace osu.Game private ScalingContainer screenContainer; + private SkinEditorContainer skinEditor; + protected override bool OnExiting() { if (ScreenStack.CurrentScreen is Loader) @@ -968,6 +974,8 @@ namespace osu.Game protected virtual void ScreenChanged(IScreen current, IScreen newScreen) { + skinEditor.Reset(); + switch (newScreen) { case IntroScreen intro: diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 4f81fad4ba..fdef4d0fce 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -4,16 +4,16 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Input.Events; using osu.Game.Graphics.Cursor; namespace osu.Game.Skinning.Editor { - public class SkinEditor : VisibilityContainer + public class SkinEditor : FocusedOverlayContainer { + public const double TRANSITION_DURATION = 500; + private readonly Drawable target; - private Container border; protected override bool StartHidden => true; @@ -25,32 +25,13 @@ namespace osu.Game.Skinning.Editor } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - border = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderColour = colours.Yellow, - BorderThickness = 5, - CornerRadius = 5, - Children = new Drawable[] - { - new Box - { - AlwaysPresent = true, - Alpha = 0, - RelativeSizeAxes = Axes.Both, - }, - } - }, new SkinBlueprintContainer(target), } }; @@ -62,29 +43,18 @@ namespace osu.Game.Skinning.Editor Show(); } - private const double transition_duration = 500; - private const float visible_target_scale = 0.8f; + protected override bool OnHover(HoverEvent e) => true; + + protected override bool OnMouseDown(MouseDownEvent e) => true; protected override void PopIn() { - if (IsLoaded) - { - target.ScaleTo(visible_target_scale, transition_duration, Easing.OutQuint); - border.ScaleTo(visible_target_scale, transition_duration, Easing.OutQuint); - } - - this.FadeIn(transition_duration); + this.FadeIn(TRANSITION_DURATION, Easing.OutQuint); } protected override void PopOut() { - if (IsLoaded) - { - target.ScaleTo(1, transition_duration, Easing.OutQuint); - border.ScaleTo(1, transition_duration, Easing.OutQuint); - } - - this.FadeOut(transition_duration, Easing.OutQuint); + this.FadeOut(TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Skinning/Editor/SkinEditorContainer.cs b/osu.Game/Skinning/Editor/SkinEditorContainer.cs new file mode 100644 index 0000000000..f84b70d67a --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinEditorContainer.cs @@ -0,0 +1,87 @@ +// 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.Input.Bindings; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; + +namespace osu.Game.Skinning.Editor +{ + /// + /// A container which handles loading a skin editor on user request. + /// + public class SkinEditorContainer : CompositeDrawable, IKeyBindingHandler + { + private readonly ScalingContainer target; + private SkinEditor skinEditor; + + private const float visible_target_scale = 0.8f; + + [Resolved] + private OsuColour colours { get; set; } + + public SkinEditorContainer(ScalingContainer target) + { + this.target = target; + RelativeSizeAxes = Axes.Both; + } + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.ToggleSkinEditor: + if (skinEditor == null) + { + LoadComponentAsync(skinEditor = new SkinEditor(target), AddInternal); + skinEditor.State.BindValueChanged(editorVisibilityChanged); + } + else + skinEditor.ToggleVisibility(); + + return true; + } + + return false; + } + + private void editorVisibilityChanged(ValueChangedEvent visibility) + { + if (visibility.NewValue == Visibility.Visible) + { + target.ScaleTo(visible_target_scale, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); + + target.Masking = true; + target.BorderThickness = 5; + target.BorderColour = colours.Yellow; + target.AllowScaling = false; + } + else + { + target.BorderThickness = 0; + target.AllowScaling = true; + + target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => target.Masking = false); + } + } + + public void OnReleased(GlobalAction action) + { + } + + /// + /// Exit any existing skin editor due to the game state changing. + /// + public void Reset() + { + skinEditor?.Hide(); + skinEditor?.Expire(); + skinEditor = null; + } + } +} From a7982787d446198a2aa95f18eab524fcd3a59d46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 17:26:55 +0900 Subject: [PATCH 322/400] Add basic header text --- osu.Game/Skinning/Editor/SkinEditor.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index fdef4d0fce..532c3de7fb 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -5,6 +5,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; namespace osu.Game.Skinning.Editor @@ -15,6 +17,8 @@ namespace osu.Game.Skinning.Editor private readonly Drawable target; + private OsuTextFlowContainer headerText; + protected override bool StartHidden => true; public SkinEditor(Drawable target) @@ -25,16 +29,31 @@ namespace osu.Game.Skinning.Editor } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + headerText = new OsuTextFlowContainer() + { + TextAnchor = Anchor.TopCentre, + Padding = new MarginPadding(20), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X + }, new SkinBlueprintContainer(target), } }; + + headerText.AddParagraph("Skin editor (preview)", cp => cp.Font = OsuFont.Default.With(size: 24)); + headerText.AddParagraph("This is a preview of what is to come. Changes are lost on changing screens.", cp => + { + cp.Font = OsuFont.Default.With(size: 12); + cp.Colour = colours.Yellow; + }); } protected override void LoadComplete() From fb64f6faf205f8c3c5d46f52fabe016ac0782ea9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 17:40:58 +0900 Subject: [PATCH 323/400] Add ability to exit using game "back" binding --- osu.Game/Skinning/Editor/SkinEditorContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinEditorContainer.cs b/osu.Game/Skinning/Editor/SkinEditorContainer.cs index f84b70d67a..adb1abefd1 100644 --- a/osu.Game/Skinning/Editor/SkinEditorContainer.cs +++ b/osu.Game/Skinning/Editor/SkinEditorContainer.cs @@ -35,6 +35,15 @@ namespace osu.Game.Skinning.Editor { switch (action) { + case GlobalAction.Back: + if (skinEditor?.State.Value == Visibility.Visible) + { + skinEditor.ToggleVisibility(); + return true; + } + + break; + case GlobalAction.ToggleSkinEditor: if (skinEditor == null) { From 63435ba5482b8c832ea5f1eab612054c5b9b1e6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 17:41:07 +0900 Subject: [PATCH 324/400] Ensure toolbar doesn't overlap editor content --- 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 67e0e6a81c..77fb9c3b63 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -619,7 +619,6 @@ namespace osu.Game logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, - skinEditor = new SkinEditorContainer(screenContainer), overlayContent = new Container { RelativeSizeAxes = Axes.Both }, rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, @@ -689,6 +688,7 @@ namespace osu.Game var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); + loadComponentSingleFile(skinEditor = new SkinEditorContainer(screenContainer), overlayContent.Add); loadComponentSingleFile(new LoginOverlay { From e716162ac242a5a36f2b435339a54472707e9ffd Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Wed, 28 Apr 2021 20:55:20 +0800 Subject: [PATCH 325/400] Fix formatting --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 13876f1648..f8dd6953ad 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -8,7 +8,6 @@ using System.Text; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.IO.Legacy; -using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; using SharpCompress.Compressors.LZMA; @@ -91,13 +90,14 @@ namespace osu.Game.Scoring.Legacy if (score.Replay != null) { - int lastTimeRounded = 0; + int lastTime = 0; + foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { // Rounding because stable could only parse integral values - int timeRounded = (int)Math.Round(f.Time); - replayData.Append(FormattableString.Invariant($"{timeRounded - lastTimeRounded}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); - lastTimeRounded = timeRounded; + int time = (int)Math.Round(f.Time); + replayData.Append(FormattableString.Invariant($"{time - lastTime}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); + lastTime = time; } } From 8d056ff38f6685ada5be49654838be7be4ad49d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 18:23:22 +0900 Subject: [PATCH 326/400] Remove redundant parenthesis --- osu.Game/Skinning/Editor/SkinEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 532c3de7fb..562dd23224 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -36,7 +36,7 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - headerText = new OsuTextFlowContainer() + headerText = new OsuTextFlowContainer { TextAnchor = Anchor.TopCentre, Padding = new MarginPadding(20), From cfbf95b4331c777a5dbd109fb57b3e5f76e2fc93 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 29 Apr 2021 14:11:35 -0700 Subject: [PATCH 327/400] Add HasPerformancePoints extension method --- ...tOnlineStatus.cs => BeatmapSetOnlineStatusExtensions.cs} | 6 ++++++ osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 4 +--- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) rename osu.Game/Beatmaps/{BeatmapSetOnlineStatus.cs => BeatmapSetOnlineStatusExtensions.cs} (61%) diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs b/osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs similarity index 61% rename from osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs rename to osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs index 5864975a2e..1de641f4f1 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs @@ -3,6 +3,12 @@ namespace osu.Game.Beatmaps { + public static class BeatmapSetOnlineStatusExtensions + { + public static bool HasPerformancePoints(this BeatmapSetOnlineStatus status) + => status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved; + } + public enum BeatmapSetOnlineStatus { None = -3, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 3e95d125de..8786cf0e63 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -59,10 +59,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); var topScore = scoreInfos.First(); - var status = topScore.Beatmap?.Status; - var showPerformanceColumn = status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved; - scoreTable.DisplayScores(scoreInfos, showPerformanceColumn); + scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.HasPerformancePoints() ?? false); scoreTable.Show(); var userScore = value.UserScore; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 5cb834b510..57df54d851 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -111,7 +111,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; - ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0; + + ppColumn.Alpha = value.Beatmap?.Status.HasPerformancePoints() ?? false ? 1 : 0; ppColumn.Text = $@"{value.PP:N0}"; statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); From 9952a5bfdb25c5130d1fe1462b5578a860d1880c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 29 Apr 2021 14:24:29 -0700 Subject: [PATCH 328/400] Revert "Fix button being recreated on importing state" This reverts commit c9967f7b7486d5744aba911a144110811b76ef04. --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 85ed3f8767..a61640a02e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -268,13 +268,11 @@ namespace osu.Game.Overlays.BeatmapSet break; case DownloadState.Downloading: + case DownloadState.Importing: // temporary to avoid showing two buttons for maps with novideo. will be fixed in new beatmap overlay design. downloadButtonsContainer.Child = new HeaderDownloadButton(BeatmapSet.Value); break; - case DownloadState.Importing: - break; - default: downloadButtonsContainer.Child = new HeaderDownloadButton(BeatmapSet.Value); if (BeatmapSet.Value.OnlineInfo.HasVideo) From 25e0fb1cf9de0e01d8225f97f737cadf43390572 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Apr 2021 01:59:59 +0300 Subject: [PATCH 329/400] Refactor OsuModBarrelRoll to allow it's usage by other rulesets --- .../Mods/OsuModBarrelRoll.cs | 40 ++------------ osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModBarrelRoll.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 37ba401d42..f63edbd99f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -2,52 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Bindables; -using osu.Framework.Extensions; -using osu.Framework.Graphics; -using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToDrawableHitObjects + public class OsuModBarrelRoll : ModBarrelRoll, IApplicableToDrawableHitObjects { - private float currentRotation; - - [SettingSource("Roll speed", "Rotations per minute")] - public BindableNumber SpinSpeed { get; } = new BindableDouble(0.5) - { - MinValue = 0.02, - MaxValue = 12, - Precision = 0.01, - }; - - [SettingSource("Direction", "The direction of rotation")] - public Bindable Direction { get; } = new Bindable(RotationDirection.Clockwise); - - public override string Name => "Barrel Roll"; - public override string Acronym => "BR"; - public override string Description => "The whole playfield is on a wheel!"; - public override double ScoreMultiplier => 1; - - public override string SettingDescription => $"{SpinSpeed.Value} rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; - - public void Update(Playfield playfield) - { - playfield.Rotation = currentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); - } - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - // scale the playfield to allow all hitobjects to stay within the visible region. - drawableRuleset.Playfield.Scale = new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X); - } + protected override Vector2 PlayfieldScale => new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X); public void ApplyToDrawableHitObjects(IEnumerable drawables) { @@ -58,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (d) { case DrawableHitCircle circle: - circle.CirclePiece.Rotation = -currentRotation; + circle.CirclePiece.Rotation = -CurrentRotation; break; } }; diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs new file mode 100644 index 0000000000..d9424563c5 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.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.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset + where TObject : HitObject + { + protected float CurrentRotation { get; private set; } + + [SettingSource("Roll speed", "Rotations per minute")] + public BindableNumber SpinSpeed { get; } = new BindableDouble(0.5) + { + MinValue = 0.02, + MaxValue = 12, + Precision = 0.01, + }; + + [SettingSource("Direction", "The direction of rotation")] + public Bindable Direction { get; } = new Bindable(RotationDirection.Clockwise); + + public override string Name => "Barrel Roll"; + public override string Acronym => "BR"; + public override string Description => "The whole playfield is on a wheel!"; + public override double ScoreMultiplier => 1; + + public override string SettingDescription => $"{SpinSpeed.Value} rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; + + /// + /// Used to allow all hitobjects to stay within the visible region. + /// + protected abstract Vector2 PlayfieldScale { get; } + + public void Update(Playfield playfield) + { + playfield.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); + } + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.Playfield.Scale = PlayfieldScale; + } + } +} From 7bf3498e2add11f5b3590b2d76df409efce1e06d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Apr 2021 02:49:19 +0300 Subject: [PATCH 330/400] Calculate playfield scale locally --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 4 ---- osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 13 +++++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index f63edbd99f..9ae9653e9b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -6,15 +6,11 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; -using osuTK; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModBarrelRoll : ModBarrelRoll, IApplicableToDrawableHitObjects { - protected override Vector2 PlayfieldScale => new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X); - public void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var d in drawables) diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index d9424563c5..4c28c730ec 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.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.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; @@ -34,11 +35,6 @@ namespace osu.Game.Rulesets.Mods public override string SettingDescription => $"{SpinSpeed.Value} rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; - /// - /// Used to allow all hitobjects to stay within the visible region. - /// - protected abstract Vector2 PlayfieldScale { get; } - public void Update(Playfield playfield) { playfield.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); @@ -46,7 +42,12 @@ namespace osu.Game.Rulesets.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.Playfield.Scale = PlayfieldScale; + // scale the playfield to allow all hitobjects to stay within the visible region. + + var playfieldSize = drawableRuleset.Playfield.DrawSize; + var minSide = MathF.Min(playfieldSize.X, playfieldSize.Y); + var maxSide = MathF.Max(playfieldSize.X, playfieldSize.Y); + drawableRuleset.Playfield.Scale = new Vector2(minSide / maxSide); } } } From e69ec91c072c7f4edac21eb944307a1999c8c485 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 11:25:39 +0900 Subject: [PATCH 331/400] Add xmldoc for `CurrentRotation` --- osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index 4c28c730ec..0d344b5269 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -15,6 +15,10 @@ namespace osu.Game.Rulesets.Mods public abstract class ModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset where TObject : HitObject { + /// + /// The current angle of rotation being applied by this mod. + /// Generally should be used to apply inverse rotation to elements which should not be rotated. + /// protected float CurrentRotation { get; private set; } [SettingSource("Roll speed", "Rotations per minute")] From cdef07b2eee4cb587fcda722febe81cd9b10366e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 13:09:57 +0900 Subject: [PATCH 332/400] Fix blueprints not hiding when deleting elements --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 17b459a916..0408ce74a6 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -20,7 +20,10 @@ namespace osu.Game.Skinning.Editor protected override void DeleteItems(IEnumerable items) { foreach (var i in items) - i.Hide(); + { + ((Drawable)i).Expire(); + SelectedItems.Remove(i); + } } protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) From e4f895b49046948816dc3f260ba399aad816f153 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 14:48:37 +0900 Subject: [PATCH 333/400] Fix editor buttons inheriting from `TriangleButton` when they have no need to --- .../Edit/Components/RadioButtons/DrawableRadioButton.cs | 4 +--- .../Edit/Components/TernaryButtons/DrawableTernaryButton.cs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 0cf7b83f3b..1f608d28fd 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.RadioButtons { - public class DrawableRadioButton : TriangleButton + public class DrawableRadioButton : OsuButton { /// /// Invoked when this has been selected. @@ -49,8 +49,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons selectedBackgroundColour = colours.BlueDark; selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f); - Triangles.Alpha = 0; - Content.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs index c72fff5c91..c43561eaa7 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.TernaryButtons { - internal class DrawableTernaryButton : TriangleButton + internal class DrawableTernaryButton : OsuButton { private Color4 defaultBackgroundColour; private Color4 defaultBubbleColour; @@ -43,8 +43,6 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons selectedBackgroundColour = colours.BlueDark; selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f); - Triangles.Alpha = 0; - Content.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, From 786ab163f622c0770c2b63a27e54db958881f499 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 30 Apr 2021 12:40:16 -0700 Subject: [PATCH 334/400] Rename extension and move to bottom of file --- ...StatusExtensions.cs => BeatmapSetOnlineStatus.cs} | 12 ++++++------ .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- .../BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game/Beatmaps/{BeatmapSetOnlineStatusExtensions.cs => BeatmapSetOnlineStatus.cs} (86%) diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs b/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs similarity index 86% rename from osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs rename to osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs index 1de641f4f1..ae5a44cfcd 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineStatusExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs @@ -3,12 +3,6 @@ namespace osu.Game.Beatmaps { - public static class BeatmapSetOnlineStatusExtensions - { - public static bool HasPerformancePoints(this BeatmapSetOnlineStatus status) - => status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved; - } - public enum BeatmapSetOnlineStatus { None = -3, @@ -20,4 +14,10 @@ namespace osu.Game.Beatmaps Qualified = 3, Loved = 4, } + + public static class BeatmapSetOnlineStatusExtensions + { + public static bool GrantsPerformancePoints(this BeatmapSetOnlineStatus status) + => status == BeatmapSetOnlineStatus.Ranked || status == BeatmapSetOnlineStatus.Approved; + } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 8786cf0e63..e9733bad20 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); var topScore = scoreInfos.First(); - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.HasPerformancePoints() ?? false); + scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() ?? false); scoreTable.Show(); var userScore = value.UserScore; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 57df54d851..a4b58e74a6 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; - ppColumn.Alpha = value.Beatmap?.Status.HasPerformancePoints() ?? false ? 1 : 0; + ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() ?? false ? 1 : 0; ppColumn.Text = $@"{value.PP:N0}"; statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); From fdf8c129474893b46e7e76b6cc1c19f835c82bcc Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 1 May 2021 11:57:47 +0800 Subject: [PATCH 335/400] Replace BeatDivisorFinder with GetClosestBeatDivisor --- .../Objects/Drawables/DrawableNote.cs | 13 +- .../UI/DrawableManiaRuleset.cs | 6 +- osu.Game.Tests/NonVisual/BeatDivisorFinder.cs | 116 ------------------ .../Rulesets/Objects/BeatDivisorFinder.cs | 55 --------- 4 files changed, 12 insertions(+), 178 deletions(-) delete mode 100644 osu.Game.Tests/NonVisual/BeatDivisorFinder.cs delete mode 100644 osu.Game/Rulesets/Objects/BeatDivisorFinder.cs diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 942a32936c..d67c360301 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -6,10 +6,10 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Skinning.Default; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private OsuColour colours { get; set; } [Resolved(canBeNull: true)] - private BeatDivisorFinder beatDivisorFinder { get; set; } + private IBeatmap beatmap { get; set; } private readonly Bindable configTimingBasedNoteColouring = new Bindable(); @@ -58,9 +58,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void LoadComplete() { - if (beatDivisorFinder != null) + if (beatmap != null) { - HitObject.StartTimeBindable.BindValueChanged(_ => snap.Value = beatDivisorFinder.FindDivisor(HitObject), true); + HitObject.StartTimeBindable.BindValueChanged(startTime => + { + snap.Value = beatmap.ControlPointInfo.GetClosestBeatDivisor(startTime.NewValue); + }, + true + ); } snap.BindValueChanged(_ => updateSnapColour()); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 0177c01240..536d7b39b7 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -45,8 +45,8 @@ namespace osu.Game.Rulesets.Mania.UI public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - [Cached] - private BeatDivisorFinder beatDivisorFinder { get; set; } + [Cached(typeof(IBeatmap))] + private ManiaBeatmap cachedBeatmap { get; set; } public IEnumerable BarLines; @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.UI : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; - beatDivisorFinder = new BeatDivisorFinder(Beatmap); + cachedBeatmap = Beatmap; } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs b/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs deleted file mode 100644 index 720d8e8ecd..0000000000 --- a/osu.Game.Tests/NonVisual/BeatDivisorFinder.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Tests.NonVisual -{ - public class BeatDivisorFinderTest - { - [Test] - public void TestFindDivisor() - { - const double beat_length = 1000; - - var beatmap = new Beatmap - { - HitObjects = new List - { - new HitObject { StartTime = -beat_length / 3 }, - new HitObject { StartTime = 0 }, - new HitObject { StartTime = beat_length / 16 }, - new HitObject { StartTime = beat_length / 12 }, - new HitObject { StartTime = beat_length / 8 }, - new HitObject { StartTime = beat_length / 6 }, - new HitObject { StartTime = beat_length / 4 }, - new HitObject { StartTime = beat_length / 3 }, - new HitObject { StartTime = beat_length / 2 }, - new HitObject { StartTime = beat_length }, - new HitObject { StartTime = beat_length + beat_length / 7 } - }, - ControlPointInfo = new ControlPointInfo() - }; - - beatmap.ControlPointInfo.Add(0, new TimingControlPoint - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = beat_length - }); - - var beatDivisorFinder = new BeatDivisorFinder(beatmap); - - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[0]), 3); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[1]), 1); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[2]), 16); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[3]), 12); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[4]), 8); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[5]), 6); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[6]), 4); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[7]), 3); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[8]), 2); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[9]), 1); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[10]), 0); - } - - [Test] - public void TestFindDivisorWithTempoChanges() - { - const double first_beat_length = 1000; - const double second_beat_length = 700; - const double third_beat_length = 200; - - const double first_beat_length_start = 0; - const double second_beat_length_start = 1000; - const double third_beat_length_start = 2000; - - var beatmap = new Beatmap - { - HitObjects = new List - { - new HitObject { StartTime = first_beat_length_start }, - new HitObject { StartTime = first_beat_length_start + first_beat_length / 2 }, - new HitObject { StartTime = second_beat_length_start }, - new HitObject { StartTime = second_beat_length_start + second_beat_length / 2 }, - new HitObject { StartTime = third_beat_length_start }, - new HitObject { StartTime = third_beat_length_start + third_beat_length / 2 }, - }, - ControlPointInfo = new ControlPointInfo() - }; - - var firstTimingControlPoint = new TimingControlPoint - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = first_beat_length - }; - - var secondTimingControlPoint = new TimingControlPoint - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = second_beat_length - }; - - var thirdTimingControlPoint = new TimingControlPoint - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = third_beat_length - }; - - beatmap.ControlPointInfo.Add(first_beat_length_start, firstTimingControlPoint); - beatmap.ControlPointInfo.Add(second_beat_length_start, secondTimingControlPoint); - beatmap.ControlPointInfo.Add(third_beat_length_start, thirdTimingControlPoint); - - var beatDivisorFinder = new BeatDivisorFinder(beatmap); - - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[0]), 1); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[1]), 2); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[2]), 1); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[3]), 2); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[4]), 1); - Assert.AreEqual(beatDivisorFinder.FindDivisor(beatmap.HitObjects[5]), 2); - } - } -} diff --git a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs b/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs deleted file mode 100644 index 1479b22942..0000000000 --- a/osu.Game/Rulesets/Objects/BeatDivisorFinder.cs +++ /dev/null @@ -1,55 +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.Utils; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Screens.Edit; - -namespace osu.Game.Rulesets.Objects -{ - /// - /// Used to find the lowest beat divisor that a aligns to in an . - /// - public class BeatDivisorFinder - { - private readonly IBeatmap beatmap; - - /// - /// Creates a new instance. - /// - /// The beatmap to use when calculating beat divisor alignment. - public BeatDivisorFinder(IBeatmap beatmap) - { - this.beatmap = beatmap; - } - - /// - /// Finds the lowest beat divisor that the given aligns to. - /// Returns 0 if it does not align to any divisor. - /// - /// The to evaluate. - public int FindDivisor(HitObject hitObject) - { - TimingControlPoint currentTimingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - double snapResult = hitObject.StartTime - currentTimingPoint.Time; - - foreach (var divisor in BindableBeatDivisor.VALID_DIVISORS) - { - if (almostDivisibleBy(snapResult, currentTimingPoint.BeatLength / divisor)) - return divisor; - } - - return 0; - } - - private const double leniency_ms = 1.0; - - private static bool almostDivisibleBy(double dividend, double divisor) - { - double remainder = Math.Abs(dividend) % divisor; - return Precision.AlmostEquals(remainder, 0, leniency_ms) || Precision.AlmostEquals(remainder - divisor, 0, leniency_ms); - } - } -} From 0d077b7a5deb9a5ec4f44c5a8335cb063080062b Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 1 May 2021 14:13:42 +0800 Subject: [PATCH 336/400] Fix GetClosestBeatDivisor returning the wrong divisor --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e47d48edcf..d3a4b635f5 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -7,6 +7,7 @@ using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Lists; +using osu.Framework.Utils; using osu.Game.Screens.Edit; namespace osu.Game.Beatmaps.ControlPoints @@ -195,7 +196,7 @@ namespace osu.Game.Beatmaps.ControlPoints { double distanceFromSnap = Math.Abs(time - getClosestSnappedTime(timingPoint, time, divisor)); - if (distanceFromSnap < closestTime) + if (Precision.DefinitelyBigger(closestTime, distanceFromSnap)) { closestDivisor = divisor; closestTime = distanceFromSnap; From 0b06c5bcb198aec5a587fa45e035ef76d0665116 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 1 May 2021 15:00:18 +0800 Subject: [PATCH 337/400] Remove unneeded test data --- .../TestSceneTimingBasedNoteColouring.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs index 5cfd7ff389..e14ad92842 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs @@ -45,15 +45,7 @@ namespace osu.Game.Rulesets.Mania.Tests new Note { StartTime = beat_length } }, ControlPointInfo = new ControlPointInfo(), - BeatmapInfo = - { - BaseDifficulty = new BeatmapDifficulty - { - SliderTickRate = 4, - OverallDifficulty = 10, - }, - Ruleset = ruleset.RulesetInfo - }, + BeatmapInfo = { Ruleset = ruleset.RulesetInfo }, }; foreach (var note in beatmap.HitObjects) @@ -62,11 +54,9 @@ namespace osu.Game.Rulesets.Mania.Tests } beatmap.ControlPointInfo.Add(0, new TimingControlPoint - { - TimeSignature = Game.Beatmaps.Timing.TimeSignatures.SimpleQuadruple, - BeatLength = beat_length - } - ); + { + BeatLength = beat_length + }); Child = new Container { From db815f793038ca0c0f58a7a70d831a4ff21d8b63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 May 2021 20:39:10 +0900 Subject: [PATCH 338/400] Tidy up implementation in `DrawableNote` --- .../Objects/Drawables/DrawableNote.cs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index d67c360301..36565e14aa 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -35,8 +35,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Drawable headPiece; - private readonly Bindable snap = new Bindable(); - public DrawableNote(Note hitObject) : base(hitObject) { @@ -58,17 +56,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void LoadComplete() { - if (beatmap != null) - { - HitObject.StartTimeBindable.BindValueChanged(startTime => - { - snap.Value = beatmap.ControlPointInfo.GetClosestBeatDivisor(startTime.NewValue); - }, - true - ); - } - - snap.BindValueChanged(_ => updateSnapColour()); + HitObject.StartTimeBindable.BindValueChanged(_ => updateSnapColour()); configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour(), true); } @@ -114,9 +102,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private void updateSnapColour() { - Colour = configTimingBasedNoteColouring.Value - ? BindableBeatDivisor.GetColourFor(snap.Value, colours) - : Color4.White; + if (beatmap == null) return; + + int snapDivisor = beatmap.ControlPointInfo.GetClosestBeatDivisor(HitObject.StartTime); + + Colour = configTimingBasedNoteColouring.Value ? BindableBeatDivisor.GetColourFor(snapDivisor, colours) : Color4.White; } } } From a551958eeb413e7e4a4b34b72cc8ed53b14f6d5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 1 May 2021 21:32:45 +0900 Subject: [PATCH 339/400] Move caching of `IBeatmap` to base `DrawableRuleset` --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ---- osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 536d7b39b7..4ee060e91e 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -45,9 +45,6 @@ namespace osu.Game.Rulesets.Mania.UI public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - [Cached(typeof(IBeatmap))] - private ManiaBeatmap cachedBeatmap { get; set; } - public IEnumerable BarLines; protected override bool RelativeScaleBeatLengths => true; @@ -80,7 +77,6 @@ namespace osu.Game.Rulesets.Mania.UI : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; - cachedBeatmap = Beatmap; } [BackgroundDependencyLoader] diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index ca27e6b21a..a2dade2627 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -85,6 +85,7 @@ namespace osu.Game.Rulesets.UI /// /// The beatmap. /// + [Cached(typeof(IBeatmap))] public readonly Beatmap Beatmap; public override IEnumerable Objects => Beatmap.HitObjects; From 137be5dc971b6f6fddc300ac9dafc0512a38295f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 1 May 2021 14:14:07 -0700 Subject: [PATCH 340/400] Use equality operator instead of null coalescing Co-Authored-By: Salman Ahmed --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- .../Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index e9733bad20..aff48919b4 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); var topScore = scoreInfos.First(); - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() ?? false); + scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); scoreTable.Show(); var userScore = value.UserScore; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index a4b58e74a6..262f321598 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; - ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() ?? false ? 1 : 0; + ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() == true ? 1 : 0; ppColumn.Text = $@"{value.PP:N0}"; statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); From 07fe99025f9e2493e86a91ac078c509675b010d2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 1 May 2021 15:05:12 +0300 Subject: [PATCH 341/400] Use bounding box of blueprint for computing selection box area --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index cb3424a250..c11c20df2a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; @@ -332,8 +333,9 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach (var blueprint in selectedBlueprints) { - topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprint.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(blueprint.SelectionQuad.BottomRight)); + var blueprintRect = blueprint.SelectionQuad.AABBFloat; + topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprintRect.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(blueprintRect.BottomRight)); } topLeft -= new Vector2(5); From 0aa17e7c955b93e4b0b2283279ebdd16fcf9e7c7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 2 May 2021 02:51:06 +0300 Subject: [PATCH 342/400] Rewrite selection box computation logic with `RectangleF`'s helper methods --- .../Compose/Components/SelectionHandler.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index c11c20df2a..8335ece236 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -328,21 +328,15 @@ namespace osu.Game.Screens.Edit.Compose.Components return; // Move the rectangle to cover the items - var topLeft = new Vector2(float.MaxValue, float.MaxValue); - var bottomRight = new Vector2(float.MinValue, float.MinValue); + RectangleF selectionRect = ToLocalSpace(selectedBlueprints.First().SelectionQuad).AABBFloat; - foreach (var blueprint in selectedBlueprints) - { - var blueprintRect = blueprint.SelectionQuad.AABBFloat; - topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprintRect.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(blueprintRect.BottomRight)); - } + foreach (var blueprint in selectedBlueprints.Skip(1)) + selectionRect = RectangleF.Union(selectionRect, ToLocalSpace(blueprint.SelectionQuad).AABBFloat); - topLeft -= new Vector2(5); - bottomRight += new Vector2(5); + selectionRect = selectionRect.Inflate(5f); - content.Size = bottomRight - topLeft; - content.Position = topLeft; + content.Position = selectionRect.Location; + content.Size = selectionRect.Size; } #endregion From b83aa0bd76e59007e7e8063aefe34bc16ed58841 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 2 May 2021 06:21:14 +0300 Subject: [PATCH 343/400] Avoid LINQ in update --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 8335ece236..917cbca4e1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -328,10 +328,10 @@ namespace osu.Game.Screens.Edit.Compose.Components return; // Move the rectangle to cover the items - RectangleF selectionRect = ToLocalSpace(selectedBlueprints.First().SelectionQuad).AABBFloat; + RectangleF selectionRect = ToLocalSpace(selectedBlueprints[0].SelectionQuad).AABBFloat; - foreach (var blueprint in selectedBlueprints.Skip(1)) - selectionRect = RectangleF.Union(selectionRect, ToLocalSpace(blueprint.SelectionQuad).AABBFloat); + for (int i = 1; i < selectedBlueprints.Count; i++) + selectionRect = RectangleF.Union(selectionRect, ToLocalSpace(selectedBlueprints[i].SelectionQuad).AABBFloat); selectionRect = selectionRect.Inflate(5f); From 3aa18e19c93272515a293946b4b468deea3fb1e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 2 May 2021 11:30:44 +0300 Subject: [PATCH 344/400] Revert "Bump Humanizer from 2.8.26 to 2.9.9" This reverts commit 1e7feff49d57fc2f0575a8a423083e0ff9a44d65. --- 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 b25e462453..1e0eabfff7 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 bbf0f6046c..e26e727e69 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -89,7 +89,7 @@ - + From 59cb5f4679aab9b861fa3ca0b2c6ed473b22d330 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Apr 2021 09:26:43 -0700 Subject: [PATCH 345/400] Get recent count from api instead --- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 6 +++--- osu.Game/Users/User.cs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 53f6d375ca..9ac9040a98 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -39,6 +39,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks case ScoreType.Firsts: return user.ScoresFirstCount; + case ScoreType.Recent: + return user.ScoresRecentCount; + default: return 0; } @@ -50,9 +53,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks drawableItemIndex = 0; base.OnItemsReceived(items); - - if (type == ScoreType.Recent) - SetCount(items.Count); } protected override APIRequest> CreateRequest() => diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 74ffb7c457..beb41c3b06 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -147,6 +147,9 @@ namespace osu.Game.Users [JsonProperty(@"scores_first_count")] public int ScoresFirstCount; + [JsonProperty(@"scores_recent_count")] + public int ScoresRecentCount; + [JsonProperty(@"beatmap_playcounts_count")] public int BeatmapPlaycountsCount; From 3e74d61dab44ea25341aebd79cd02fe6792c1a05 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Apr 2021 09:27:32 -0700 Subject: [PATCH 346/400] Add best count from api --- .../Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs | 3 +++ osu.Game/Users/User.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 9ac9040a98..d06e0c87fc 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -36,6 +36,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { switch (type) { + case ScoreType.Best: + return user.ScoresBestCount; + case ScoreType.Firsts: return user.ScoresFirstCount; diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index beb41c3b06..2e04693e82 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -144,6 +144,9 @@ namespace osu.Game.Users [JsonProperty(@"unranked_beatmapset_count")] public int UnrankedBeatmapsetCount; + [JsonProperty(@"scores_best_count")] + public int ScoresBestCount; + [JsonProperty(@"scores_first_count")] public int ScoresFirstCount; From cc056088bdf0cbf988f7b8185addbedd669ebb95 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 2 May 2021 14:31:06 -0700 Subject: [PATCH 347/400] Update profile subsections to use counters instead of missing text in line with web --- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 2 +- .../Historical/PaginatedMostPlayedBeatmapContainer.cs | 2 +- osu.Game/Overlays/Profile/Sections/HistoricalSection.cs | 2 +- .../Overlays/Profile/Sections/PaginatedProfileSubsection.cs | 4 ++-- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 4 ++-- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 780d7ea986..fe9c710bcc 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps private readonly BeatmapSetType type; public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string headerText) - : base(user, headerText, "", CounterVisibilityState.AlwaysVisible) + : base(user, headerText) { this.type = type; ItemsPerPage = 6; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index e5bb1f8008..eeb14e5e4f 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical public class PaginatedMostPlayedBeatmapContainer : PaginatedProfileSubsection { public PaginatedMostPlayedBeatmapContainer(Bindable user) - : base(user, "Most Played Beatmaps", "No records. :(", CounterVisibilityState.AlwaysVisible) + : base(user, "Most Played Beatmaps") { ItemsPerPage = 5; } diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index 6e2b9873cf..4fbb7fc7d7 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Profile.Sections { new PlayHistorySubsection(User), new PaginatedMostPlayedBeatmapContainer(User), - new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", CounterVisibilityState.VisibleWhenZero), + new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)"), new ReplaysSubsection(User) }; } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 51e5622f68..e237b43b2e 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -38,8 +38,8 @@ namespace osu.Game.Overlays.Profile.Sections private OsuSpriteText missing; private readonly string missingText; - protected PaginatedProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) - : base(user, headerText, counterVisibilityState) + protected PaginatedProfileSubsection(Bindable user, string headerText = "", string missingText = "") + : base(user, headerText, CounterVisibilityState.AlwaysVisible) { this.missingText = missingText; } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index d06e0c87fc..720cd4a3db 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -18,8 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly ScoreType type; - public PaginatedScoreContainer(ScoreType type, Bindable user, string headerText, CounterVisibilityState counterVisibilityState, string missingText = "") - : base(user, headerText, missingText, counterVisibilityState) + public PaginatedScoreContainer(ScoreType type, Bindable user, string headerText) + : base(user, headerText) { this.type = type; diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index e41e414893..33f7c2f71a 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -16,8 +16,8 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance", CounterVisibilityState.AlwaysHidden, "No performance records. :("), - new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks", CounterVisibilityState.AlwaysVisible) + new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance"), + new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks") }; } } From b2130fc600d8cff184ba6e51bc3ef7dbeed2f44c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 May 2021 01:56:32 +0300 Subject: [PATCH 348/400] Fix replay frames sort instability --- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 0f25a45177..d6c9b9c6d9 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -4,7 +4,7 @@ #nullable enable using System; -using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using osu.Game.Input.Handlers; using osu.Game.Replays; @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Replays { // TODO: This replay frame ordering should be enforced on the Replay type. // Currently, the ordering can be broken if the frames are added after this construction. - replay.Frames.Sort((x, y) => x.Time.CompareTo(y.Time)); + replay.Frames = replay.Frames.OrderBy(f => f.Time).ToList(); this.replay = replay; currentFrameIndex = -1; From 943c497397245fdf1924860b951c89940e3b235e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 May 2021 02:02:14 +0300 Subject: [PATCH 349/400] Return back removed using --- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index d6c9b9c6d9..bc8994bbe5 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Game.Input.Handlers; From 8c9cfb63013df07e0942e07fdb10918299cc034d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:28:35 +0900 Subject: [PATCH 350/400] Remove unsafe access to Composer.HitObjects --- .../Edit/Compose/Components/EditorBlueprintContainer.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 2a605f75d8..29dfff0e96 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -34,13 +34,6 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - // For non-pooled rulesets, hitobjects are already present in the playfield which allows the blueprints to be loaded in the async context. - if (Composer != null) - { - foreach (var obj in Composer.HitObjects) - AddBlueprintFor(obj.HitObject); - } - selectedHitObjects.BindTo(Beatmap.SelectedHitObjects); selectedHitObjects.CollectionChanged += (selectedObjects, args) => { From f3b305bbe62f4501579cd36ca6e77c188fb344ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:58:23 +0900 Subject: [PATCH 351/400] Rename and improve xmldoc of `SkinEditorOverlay` --- osu.Game/OsuGame.cs | 4 ++-- .../{SkinEditorContainer.cs => SkinEditorOverlay.cs} | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) rename osu.Game/Skinning/Editor/{SkinEditorContainer.cs => SkinEditorOverlay.cs} (91%) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 77fb9c3b63..d031a8e7fa 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -688,7 +688,7 @@ namespace osu.Game var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true); loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); - loadComponentSingleFile(skinEditor = new SkinEditorContainer(screenContainer), overlayContent.Add); + loadComponentSingleFile(skinEditor = new SkinEditorOverlay(screenContainer), overlayContent.Add); loadComponentSingleFile(new LoginOverlay { @@ -946,7 +946,7 @@ namespace osu.Game private ScalingContainer screenContainer; - private SkinEditorContainer skinEditor; + private SkinEditorOverlay skinEditor; protected override bool OnExiting() { diff --git a/osu.Game/Skinning/Editor/SkinEditorContainer.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs similarity index 91% rename from osu.Game/Skinning/Editor/SkinEditorContainer.cs rename to osu.Game/Skinning/Editor/SkinEditorOverlay.cs index adb1abefd1..06c6dffb60 100644 --- a/osu.Game/Skinning/Editor/SkinEditorContainer.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -13,9 +13,10 @@ using osu.Game.Input.Bindings; namespace osu.Game.Skinning.Editor { /// - /// A container which handles loading a skin editor on user request. + /// A container which handles loading a skin editor on user request for a specified target. + /// This also handles the scaling / positioning adjustment of the target. /// - public class SkinEditorContainer : CompositeDrawable, IKeyBindingHandler + public class SkinEditorOverlay : CompositeDrawable, IKeyBindingHandler { private readonly ScalingContainer target; private SkinEditor skinEditor; @@ -25,7 +26,7 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } - public SkinEditorContainer(ScalingContainer target) + public SkinEditorOverlay(ScalingContainer target) { this.target = target; RelativeSizeAxes = Axes.Both; From 3dd4b7b746ab72532ec0e5db0777edcce99afc6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:08:34 +0900 Subject: [PATCH 352/400] Fix use of non-existent word in `OsuFocusedOverlayContainer` xmldoc --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index e168f265dd..c0518247a9 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Graphics.Containers protected override bool BlockNonPositionalInput => true; /// - /// Temporary to allow for overlays in the main screen content to not dim theirselves. + /// Temporary to allow for overlays in the main screen content to not dim themselves. /// Should be eventually replaced by dimming which is aware of the target dim container (traverse parent for certain interface type?). /// protected virtual bool DimMainContent => true; From 01984de9c7ce603e063f914942775d55fe8793e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:13:32 +0900 Subject: [PATCH 353/400] Use existing `GetStateFromSelection` helper function --- .../Compose/Components/EditorSelectionHandler.cs | 11 ----------- .../Edit/Compose/Components/SelectionHandler.cs | 11 +++++++++++ osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 14 +------------- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 0fc305dcc4..6ab4ca8267 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -108,17 +108,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - /// - /// Given a selection target and a function of truth, retrieve the correct ternary state for display. - /// - protected TernaryState GetStateFromSelection(IEnumerable selection, Func func) - { - if (selection.Any(func)) - return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; - - return TernaryState.False; - } - #endregion #region Ternary state changes diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index cb3424a250..c0dbc5e7db 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -268,6 +268,17 @@ namespace osu.Game.Screens.Edit.Compose.Components DeleteSelected(); } + /// + /// Given a selection target and a function of truth, retrieve the correct ternary state for display. + /// + protected static TernaryState GetStateFromSelection(IEnumerable selection, Func func) + { + if (selection.Any(func)) + return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; + + return TernaryState.False; + } + /// /// Called whenever the deletion of items has been requested. /// diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 0408ce74a6..044ad88333 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -53,21 +53,9 @@ namespace osu.Game.Skinning.Editor return displayableAnchors.Select(a => { - var countExisting = selection.Count(b => ((Drawable)b.Item).Anchor == a); - var countTotal = selection.Count(); - - TernaryState state; - - if (countExisting == countTotal) - state = TernaryState.True; - else if (countExisting > 0) - state = TernaryState.Indeterminate; - else - state = TernaryState.False; - return new AnchorMenuItem(a, selection, _ => applyAnchor(a)) { - State = { Value = state } + State = { Value = GetStateFromSelection(selection, c => ((Drawable)c.Item).Anchor == a) } }; }); } From a2faa0b74c5c10b89d5f06ded55abe6d68c9bfbc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:13:53 +0900 Subject: [PATCH 354/400] Remove dead code --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 044ad88333..8792daef19 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -125,11 +125,6 @@ namespace osu.Game.Skinning.Editor { } - private void updateState(TernaryState obj) - { - throw new NotImplementedException(); - } - private static TernaryState getNextState(TernaryState state) => TernaryState.True; } } From 51f4077b2714b61db68db48706546c86a1125238 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:15:00 +0900 Subject: [PATCH 355/400] Reorder methods in `SkinSelectionHandler` to follow standards --- .../Skinning/Editor/SkinSelectionHandler.cs | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 8792daef19..c7c0f45cc0 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -17,6 +17,46 @@ namespace osu.Game.Skinning.Editor { public class SkinSelectionHandler : SelectionHandler { + public override bool HandleRotation(float angle) + { + // TODO: this doesn't correctly account for origin/anchor specs being different in a multi-selection. + foreach (var c in SelectedBlueprints) + ((Drawable)c.Item).Rotation += angle; + + return base.HandleRotation(angle); + } + + public override bool HandleScale(Vector2 scale, Anchor anchor) + { + adjustScaleFromAnchor(ref scale, anchor); + + foreach (var c in SelectedBlueprints) + ((Drawable)c.Item).Scale += scale * 0.01f; + + return true; + } + + public override bool HandleMovement(MoveSelectionEvent moveEvent) + { + foreach (var c in SelectedBlueprints) + { + Drawable drawable = (Drawable)c.Item; + drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); + } + + return true; + } + + protected override void OnSelectionChanged() + { + base.OnSelectionChanged(); + + SelectionBox.CanRotate = true; + SelectionBox.CanScaleX = true; + SelectionBox.CanScaleY = true; + SelectionBox.CanReverse = false; + } + protected override void DeleteItems(IEnumerable items) { foreach (var i in items) @@ -67,46 +107,6 @@ namespace osu.Game.Skinning.Editor ((Drawable)item).Anchor = anchor; } - protected override void OnSelectionChanged() - { - base.OnSelectionChanged(); - - SelectionBox.CanRotate = true; - SelectionBox.CanScaleX = true; - SelectionBox.CanScaleY = true; - SelectionBox.CanReverse = false; - } - - public override bool HandleRotation(float angle) - { - // TODO: this doesn't correctly account for origin/anchor specs being different in a multi-selection. - foreach (var c in SelectedBlueprints) - ((Drawable)c.Item).Rotation += angle; - - return base.HandleRotation(angle); - } - - public override bool HandleScale(Vector2 scale, Anchor anchor) - { - adjustScaleFromAnchor(ref scale, anchor); - - foreach (var c in SelectedBlueprints) - ((Drawable)c.Item).Scale += scale * 0.01f; - - return true; - } - - public override bool HandleMovement(MoveSelectionEvent moveEvent) - { - foreach (var c in SelectedBlueprints) - { - Drawable drawable = (Drawable)c.Item; - drawable.Position += drawable.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); - } - - return true; - } - private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) { // cancel out scale in axes we don't care about (based on which drag handle was used). From f36684a070308f6068a709b68dba2d67906bb632 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:17:01 +0900 Subject: [PATCH 356/400] Guard against non-threadsafe transformation logic in `ScalingContainer`- --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index b691e372c5..2488fd14d0 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.Containers return; allowScaling = value; - updateSize(); + if (IsLoaded) updateSize(); } } From df8609b3dc6307a7d9e91b5f6a2df13a2b5d824d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:15:50 +0900 Subject: [PATCH 357/400] Move private field for skin editor overlay to where others exist --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d031a8e7fa..b1173784b5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -80,6 +80,8 @@ namespace osu.Game private BeatmapSetOverlay beatmapSetOverlay; + private SkinEditorOverlay skinEditor; + [Cached] private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender(); @@ -946,8 +948,6 @@ namespace osu.Game private ScalingContainer screenContainer; - private SkinEditorOverlay skinEditor; - protected override bool OnExiting() { if (ScreenStack.CurrentScreen is Loader) From a298a930701b337248823aa901364786c4ef19d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:18:18 +0900 Subject: [PATCH 358/400] Remove redundant storage of blueprint's related item --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index 491a403325..11409c46ab 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Screens.Play.HUD; using osuTK; @@ -17,26 +16,20 @@ namespace osu.Game.Skinning.Editor { public class SkinBlueprint : SelectionBlueprint { - /// - /// The which this applies to. - /// - public readonly ISkinnableComponent Component; - private Container box; - private Drawable drawable => (Drawable)Component; + private Drawable drawable => (Drawable)Item; /// - /// Whether the blueprint should be shown even when the is not alive. + /// Whether the blueprint should be shown even when the is not alive. /// protected virtual bool AlwaysShowWhenSelected => false; - protected override bool ShouldBeAlive => (drawable.IsAlive && Component.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); + protected override bool ShouldBeAlive => (drawable.IsAlive && Item.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); public SkinBlueprint(ISkinnableComponent component) : base(component) { - Component = component; } [BackgroundDependencyLoader] From 7d8be8cd836dda2a401f4de40d70a680f7e6e958 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:20:00 +0900 Subject: [PATCH 359/400] Add comment about why we are running `checkForComponents` on a timer --- osu.Game/Skinning/Editor/SkinBlueprintContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index f2bc8ecddf..d9bfefe5f2 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -30,6 +30,8 @@ namespace osu.Game.Skinning.Editor { foreach (var c in target.ChildrenOfType().ToArray()) AddBlueprintFor(c); + // We'd hope to eventually be running this in a more sensible way, but this handles situations where new drawables become present (ie. during ongoing gameplay) + // or when drawables in the target are loaded asynchronously and may not be immediately available when this BlueprintContainer is loaded. Scheduler.AddDelayed(checkForComponents, 1000); } From 15603de6e946c4570cb3c9befc68114e162a0c11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:24:51 +0900 Subject: [PATCH 360/400] Change scale multiplier to be closer to expectations --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index c7c0f45cc0..d09ba8af0e 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -31,7 +31,8 @@ namespace osu.Game.Skinning.Editor adjustScaleFromAnchor(ref scale, anchor); foreach (var c in SelectedBlueprints) - ((Drawable)c.Item).Scale += scale * 0.01f; + // TODO: this is temporary and will be fixed with a separate refactor of selection transform logic. + ((Drawable)c.Item).Scale += scale * 0.02f; return true; } From 4cfa858dc4cd02cbf438ea9863d57a1c26568c63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:37:15 +0900 Subject: [PATCH 361/400] Fix tooltips displaying for hidden `SelectionHandler` content --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index c0dbc5e7db..053e532137 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -57,7 +57,6 @@ namespace osu.Game.Screens.Edit.Compose.Components RelativeSizeAxes = Axes.Both; AlwaysPresent = true; - Alpha = 0; } [BackgroundDependencyLoader] @@ -318,7 +317,7 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty; - this.FadeTo(count > 0 ? 1 : 0); + content.FadeTo(count > 0 ? 1 : 0); OnSelectionChanged(); } From 839ac968a9623bbabaabf3ea1d5087ef78f0ad7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 15:37:15 +0900 Subject: [PATCH 362/400] Fix tooltips displaying for hidden `SelectionHandler` content --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 917cbca4e1..f207e27a5e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -58,7 +58,6 @@ namespace osu.Game.Screens.Edit.Compose.Components RelativeSizeAxes = Axes.Both; AlwaysPresent = true; - Alpha = 0; } [BackgroundDependencyLoader] @@ -308,7 +307,7 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty; - this.FadeTo(count > 0 ? 1 : 0); + content.FadeTo(count > 0 ? 1 : 0); OnSelectionChanged(); } From 3268a75f05afc1515b3feb874a4aa72bfb1d0173 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 17:34:34 +0900 Subject: [PATCH 363/400] Remove intermediate container to fix tests --- .../Editing/TestSceneEditorClipboard.cs | 4 +- .../Edit/Compose/Components/SelectionBox.cs | 48 ++++++++++++++++--- .../Compose/Components/SelectionHandler.cs | 42 ++-------------- 3 files changed, 48 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 3a063af843..3aff74a0a8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -132,8 +132,8 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear()); - AddUntilStep("timeline selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); - AddUntilStep("composer selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); + AddUntilStep("timeline selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); + AddUntilStep("composer selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); } AddStep("paste hitobject", () => Editor.Paste()); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 9d6b44e207..0d6cfc0689 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Input; @@ -16,6 +17,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { public class SelectionBox : CompositeDrawable { + public const float BORDER_RADIUS = 3; + public Func OnRotation; public Func OnScale; public Func OnFlip; @@ -92,21 +95,32 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private string text; + + public string Text + { + get => text; + set + { + if (value == text) + return; + + text = value; + if (selectionDetailsText != null) + selectionDetailsText.Text = value; + } + } + private Container dragHandles; private FillFlowContainer buttons; - public const float BORDER_RADIUS = 3; + private OsuSpriteText selectionDetailsText; [Resolved] private OsuColour colours { get; set; } [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.Both; - - recreate(); - } + private void load() => recreate(); protected override bool OnKeyDown(KeyDownEvent e) { @@ -144,6 +158,26 @@ namespace osu.Game.Screens.Edit.Compose.Components InternalChildren = new Drawable[] { + new Container + { + Name = "info text", + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colours.YellowDark, + RelativeSizeAxes = Axes.Both, + }, + selectionDetailsText = new OsuSpriteText + { + Padding = new MarginPadding(2), + Colour = colours.Gray0, + Font = OsuFont.Default.With(size: 11), + Text = text, + } + } + }, new Container { Masking = true, diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index f207e27a5e..ce0a72a012 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -10,13 +10,11 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osuTK; @@ -43,10 +41,6 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly List> selectedBlueprints; - private Drawable content; - - private OsuSpriteText selectionDetailsText; - protected SelectionBox SelectionBox { get; private set; } [Resolved(CanBeNull = true)] @@ -63,33 +57,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChild = content = new Container - { - Children = new Drawable[] - { - // todo: should maybe be inside the SelectionBox? - new Container - { - Name = "info text", - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = colours.YellowDark, - RelativeSizeAxes = Axes.Both, - }, - selectionDetailsText = new OsuSpriteText - { - Padding = new MarginPadding(2), - Colour = colours.Gray0, - Font = OsuFont.Default.With(size: 11) - } - } - }, - SelectionBox = CreateSelectionBox(), - } - }; + InternalChild = SelectionBox = CreateSelectionBox(); SelectedItems.CollectionChanged += (sender, args) => { @@ -305,9 +273,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { int count = SelectedItems.Count; - selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty; + SelectionBox.Text = count > 0 ? count.ToString() : string.Empty; - content.FadeTo(count > 0 ? 1 : 0); + SelectionBox.FadeTo(count > 0 ? 1 : 0); OnSelectionChanged(); } @@ -334,8 +302,8 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionRect = selectionRect.Inflate(5f); - content.Position = selectionRect.Location; - content.Size = selectionRect.Size; + SelectionBox.Position = selectionRect.Location; + SelectionBox.Size = selectionRect.Size; } #endregion From b28e1569caad7b6a1781dc32129f57a9d74cc92c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 20:09:48 +0900 Subject: [PATCH 364/400] Remove no-longer-relevant matching comment --- .../Screens/Edit/Compose/Components/EditorBlueprintContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index 29dfff0e96..db322faf65 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -62,7 +62,6 @@ namespace osu.Game.Screens.Edit.Compose.Components if (Composer != null) { - // For pooled rulesets, blueprints must be added for hitobjects already "current" as they would've not been "current" during the async load addition process above. foreach (var obj in Composer.HitObjects) AddBlueprintFor(obj.HitObject); From e00af3e71d2f2f754e641698dede05e3ac089b5b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 4 May 2021 09:45:58 +0300 Subject: [PATCH 365/400] Add test coverage --- .../NonVisual/FramedReplayInputHandlerTest.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index a42b7d54ee..2062c4b820 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; +using osu.Framework.Utils; using osu.Game.Replays; using osu.Game.Rulesets.Replays; @@ -278,6 +280,38 @@ namespace osu.Game.Tests.NonVisual setTime(-100, -100); } + [Test] + public void TestReplayFrameSortStability() + { + const double repeating_time = 5000; + + int data = 0; + + // 1. add a range of frames in which some of them have the constant time 5000, all without any "data". + // 2. randomize the frames. + // 3. assign "data" to each frame in ascending order. + replay.Frames.AddRange(Enumerable.Range(1, 250).Select(i => + { + if (RNG.NextBool()) + return new TestReplayFrame(repeating_time, true); + else + return new TestReplayFrame(i * 1000, true); + }).OrderBy(_ => RNG.Next()).Select(f => new TestReplayFrame(f.Time, true, ++data))); + + replay.HasReceivedAllFrames = true; + + // create a new handler with the replay for the frames to be sorted. + handler = new TestInputHandler(replay); + + // ensure sort stability by checking whether the "data" assigned to each time-repeated frame is in ascending order, as it was before sort. + var repeatingTimeFramesData = replay.Frames + .Cast() + .Where(f => f.Time == repeating_time) + .Select(f => f.Data); + + Assert.That(repeatingTimeFramesData, Is.Ordered.Ascending); + } + private void setReplayFrames() { replay.Frames = new List @@ -324,11 +358,13 @@ namespace osu.Game.Tests.NonVisual private class TestReplayFrame : ReplayFrame { public readonly bool IsImportant; + public readonly int Data; - public TestReplayFrame(double time, bool isImportant = false) + public TestReplayFrame(double time, bool isImportant = false, int data = 0) : base(time) { IsImportant = isImportant; + Data = data; } } From ffc88db47aa7377e78a6db933723a314a937e933 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 May 2021 16:04:59 +0900 Subject: [PATCH 366/400] Implement Duration via the interface --- osu.Game/Storyboards/IStoryboardElementWithDuration.cs | 2 +- osu.Game/Storyboards/StoryboardSprite.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/IStoryboardElementWithDuration.cs b/osu.Game/Storyboards/IStoryboardElementWithDuration.cs index 02b438cb76..55f163ee07 100644 --- a/osu.Game/Storyboards/IStoryboardElementWithDuration.cs +++ b/osu.Game/Storyboards/IStoryboardElementWithDuration.cs @@ -16,6 +16,6 @@ namespace osu.Game.Storyboards /// /// The duration of the StoryboardElement. /// - double Duration { get; } + double Duration => EndTime - StartTime; } } diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 0aaf264341..bf87e7d10e 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -65,8 +65,6 @@ namespace osu.Game.Storyboards } } - public double Duration => EndTime - StartTime; - public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands); private delegate void DrawablePropertyInitializer(Drawable drawable, T value); From b30145de40646e893d3123133fd46d67e52c23b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 May 2021 16:35:50 +0900 Subject: [PATCH 367/400] Specify explicit primitive type --- 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 1a11adad30..d424f90079 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -640,7 +640,7 @@ namespace osu.Game.Screens.Play return; } - var storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; + bool storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value; if (storyboardHasOutro) { From 4c7a4239f818662055b08f610b7ab588a3520042 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 May 2021 16:36:05 +0900 Subject: [PATCH 368/400] Fix `AllowGameplayOverlays` potentially not working for outro skip overlay --- osu.Game/Screens/Play/Player.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d424f90079..6ed1a87d8a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -245,8 +245,6 @@ namespace osu.Game.Screens.Play HUDOverlay.ShowHud.Value = false; HUDOverlay.ShowHud.Disabled = true; BreakOverlay.Hide(); - skipIntroOverlay.Hide(); - skipOutroOverlay.Hide(); } DrawableRuleset.FrameStableClock.WaitingOnFrames.BindValueChanged(waiting => @@ -398,7 +396,7 @@ namespace osu.Game.Screens.Play } }; - if (!Configuration.AllowSkipping) + if (!Configuration.AllowSkipping || !DrawableRuleset.AllowGameplayOverlays) { skipIntroOverlay.Expire(); skipOutroOverlay.Expire(); From b380be7169de5b4e2ca066d27fd5970a624e5cfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 May 2021 16:43:51 +0900 Subject: [PATCH 369/400] Add xmldoc for `updateCompletionState` --- osu.Game/Screens/Play/Player.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6ed1a87d8a..cf26bc479a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -582,6 +582,11 @@ namespace osu.Game.Screens.Play private ScheduledDelegate completionProgressDelegate; private Task prepareScoreForDisplayTask; + /// + /// Handles changes in player state which may progress the completion of gameplay / this screen's lifetime. + /// + /// If in a state where a storyboard outro is to be played, offers the choice of skipping beyond it. + /// Thrown if this method is called more than once without changing state. private void updateCompletionState(bool skipStoryboardOutro = false) { // screen may be in the exiting transition phase. From 18779b1d1e8fa6d6b9fdb4e0d389b8b7f0d08d54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 May 2021 16:48:13 +0900 Subject: [PATCH 370/400] Cache last event time value to avoid super expensive LINQ --- .../Drawables/DrawableStoryboard.cs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index a9a8b8a4ac..4c42823779 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -20,6 +20,13 @@ namespace osu.Game.Storyboards.Drawables [Cached] public Storyboard Storyboard { get; } + /// + /// Whether the storyboard is considered finished. + /// + public IBindable HasStoryboardEnded => hasStoryboardEnded; + + private readonly BindableBool hasStoryboardEnded = new BindableBool(); + protected override Container Content { get; } protected override Vector2 DrawScale => new Vector2(Parent.DrawHeight / 480); @@ -40,6 +47,8 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveCompletedTransforms => false; + private double? lastEventEndTime; + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => @@ -74,6 +83,14 @@ namespace osu.Game.Storyboards.Drawables Add(layer.CreateDrawable()); } + + lastEventEndTime = Storyboard.LatestEventTime; + } + + protected override void Update() + { + base.Update(); + hasStoryboardEnded.Value = lastEventEndTime == null || Time.Current >= lastEventEndTime; } public DrawableStoryboardLayer OverlayLayer => Children.Single(layer => layer.Name == "Overlay"); @@ -83,25 +100,5 @@ namespace osu.Game.Storyboards.Drawables foreach (var layer in Children) layer.Enabled = passing ? layer.Layer.VisibleWhenPassing : layer.Layer.VisibleWhenFailing; } - - protected override void Update() - { - base.Update(); - updateHasStoryboardEnded(); - } - - /// - /// Whether the storyboard is considered finished. - /// - public IBindable HasStoryboardEnded => hasStoryboardEnded; - - private readonly BindableBool hasStoryboardEnded = new BindableBool(); - - private void updateHasStoryboardEnded() - { - hasStoryboardEnded.Value = - Storyboard.LatestEventTime == null || - Time.Current >= Storyboard.LatestEventTime; - } } } From acc9258eb217dfd04dfdc71f32498df3884caba6 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 4 May 2021 00:59:22 -0700 Subject: [PATCH 371/400] Implement notes for settings --- osu.Game/Overlays/Settings/SettingsItem.cs | 33 +++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 0bd9750b0b..1d6535c289 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -19,6 +19,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Settings { @@ -36,6 +37,8 @@ namespace osu.Game.Overlays.Settings private SpriteText labelText; + private readonly OsuTextFlowContainer noteText; + public bool ShowsDefaultIndicator = true; public string TooltipText { get; set; } @@ -57,6 +60,19 @@ namespace osu.Game.Overlays.Settings } } + /// + /// Text to be displayed at the bottom of this . + /// Used for further explanation or indicating drawbacks of the current setting. + /// + public string NoteText + { + set + { + noteText.Alpha = 1; + noteText.Text = value; + } + } + public virtual Bindable Current { get => controlWithCurrent.Current; @@ -92,7 +108,16 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, - Child = Control = CreateControl() + Children = new[] + { + Control = CreateControl(), + noteText = new OsuTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + }, + }, }, }; @@ -108,6 +133,12 @@ namespace osu.Game.Overlays.Settings } } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + noteText.Colour = colours.Yellow; + } + private void updateDisabled() { if (labelText != null) From 796bd8e47eab8b8268bcbecae9349539b99d85a3 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 4 May 2021 00:59:48 -0700 Subject: [PATCH 372/400] Add existing setting notes from stable --- .../Settings/Sections/Graphics/LayoutSettings.cs | 9 ++++++++- .../Sections/Graphics/RendererSettings.cs | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 4d5c2e06eb..a24dd1f64b 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -141,7 +141,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); - windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown(); + windowModeDropdown.Current.BindValueChanged(mode => + { + updateResolutionDropdown(); + + const string not_fullscreen_note = "Running without fullscreen mode will increase your input latency!"; + + windowModeDropdown.NoteText = mode.NewValue != WindowMode.Fullscreen ? not_fullscreen_note : string.Empty; + }, true); windowModes.BindCollectionChanged((sender, args) => { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 8773e6763c..3ad201640d 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -13,6 +13,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { protected override string Header => "Renderer"; + private SettingsEnumDropdown frameLimiterDropdown; + [BackgroundDependencyLoader] private void load(FrameworkConfigManager config, OsuConfigManager osuConfig) { @@ -20,7 +22,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics Children = new Drawable[] { // TODO: this needs to be a custom dropdown at some point - new SettingsEnumDropdown + frameLimiterDropdown = new SettingsEnumDropdown { LabelText = "Frame limiter", Current = config.GetBindable(FrameworkSetting.FrameSync) @@ -37,5 +39,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + frameLimiterDropdown.Current.BindValueChanged(limit => + { + const string unlimited_frames_note = "Using unlimited frame limiter can lead to stutters. \"2x refresh rate\" is recommended."; + + frameLimiterDropdown.NoteText = limit.NewValue == FrameSync.Unlimited ? unlimited_frames_note : string.Empty; + }, true); + } } } From 0a649227385b82fb95787e2fd8989fae1fca258a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 4 May 2021 01:00:35 -0700 Subject: [PATCH 373/400] Add supporter note to background source setting --- .../UserInterface/MainMenuSettings.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index 95e2e9da30..707f8cd314 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -2,8 +2,11 @@ // 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.Configuration; +using osu.Game.Online.API; +using osu.Game.Users; namespace osu.Game.Overlays.Settings.Sections.UserInterface { @@ -11,9 +14,15 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface { protected override string Header => "Main Menu"; + private IBindable user; + + private SettingsEnumDropdown backgroundSourceDropdown; + [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, IAPIProvider api) { + user = api.LocalUser.GetBoundCopy(); + Children = new Drawable[] { new SettingsCheckbox @@ -31,7 +40,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = "Intro sequence", Current = config.GetBindable(OsuSetting.IntroSequence), }, - new SettingsEnumDropdown + backgroundSourceDropdown = new SettingsEnumDropdown { LabelText = "Background source", Current = config.GetBindable(OsuSetting.MenuBackgroundSource), @@ -43,5 +52,17 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + backgroundSourceDropdown.Current.BindValueChanged(source => + { + const string not_supporter_note = "Changes to this setting will only apply with an active osu!supporter tag."; + + backgroundSourceDropdown.NoteText = user.Value?.IsSupporter == false ? not_supporter_note : string.Empty; + }, true); + } } } From a5842130027d8e9560c9311e997c4916ebc35ba7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 4 May 2021 09:11:33 -0700 Subject: [PATCH 374/400] Use vertical padding instead of relative height for default button --- osu.Game/Overlays/Settings/SettingsItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 1d6535c289..6405431f6b 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -18,7 +18,6 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osuTK; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Settings @@ -172,6 +171,7 @@ namespace osu.Game.Overlays.Settings { RelativeSizeAxes = Axes.Y; Width = SettingsPanel.CONTENT_MARGINS; + Padding = new MarginPadding { Vertical = 1.5f }; Alpha = 0f; } @@ -194,7 +194,7 @@ namespace osu.Game.Overlays.Settings Type = EdgeEffectType.Glow, Radius = 2, }, - Size = new Vector2(0.33f, 0.8f), + Width = 0.33f, Child = new Box { RelativeSizeAxes = Axes.Both }, }; } From 4ceb9b1562e52475ba8da9652bd2d31501983ef3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 4 May 2021 23:36:14 +0300 Subject: [PATCH 375/400] Avoid randomizing and overestimating logic with simple hardcoding Not sure what was in my mind while I was pushing that.. --- .../NonVisual/FramedReplayInputHandlerTest.cs | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 2062c4b820..fe1186bf95 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Utils; using osu.Game.Replays; using osu.Game.Rulesets.Replays; @@ -281,26 +280,39 @@ namespace osu.Game.Tests.NonVisual } [Test] - public void TestReplayFrameSortStability() + public void TestReplayFramesSortStability() { const double repeating_time = 5000; - int data = 0; - - // 1. add a range of frames in which some of them have the constant time 5000, all without any "data". - // 2. randomize the frames. - // 3. assign "data" to each frame in ascending order. - replay.Frames.AddRange(Enumerable.Range(1, 250).Select(i => + // add a range of frames randomized in time but have a "data" assigned to them in ascending order. + replay.Frames.AddRange(new[] { - if (RNG.NextBool()) - return new TestReplayFrame(repeating_time, true); - else - return new TestReplayFrame(i * 1000, true); - }).OrderBy(_ => RNG.Next()).Select(f => new TestReplayFrame(f.Time, true, ++data))); + new TestReplayFrame(repeating_time, true, 0), + new TestReplayFrame(0, true, 1), + new TestReplayFrame(3000, true, 2), + new TestReplayFrame(repeating_time, true, 3), + new TestReplayFrame(repeating_time, true, 4), + new TestReplayFrame(6000, true, 5), + new TestReplayFrame(9000, true, 6), + new TestReplayFrame(repeating_time, true, 7), + new TestReplayFrame(repeating_time, true, 8), + new TestReplayFrame(1000, true, 9), + new TestReplayFrame(11000, true, 10), + new TestReplayFrame(21000, true, 11), + new TestReplayFrame(4000, true, 12), + new TestReplayFrame(repeating_time, true, 13), + new TestReplayFrame(repeating_time, true, 14), + new TestReplayFrame(8000, true, 15), + new TestReplayFrame(2000, true, 16), + new TestReplayFrame(7000, true, 17), + new TestReplayFrame(repeating_time, true, 18), + new TestReplayFrame(repeating_time, true, 19), + new TestReplayFrame(10000, true, 20), + }); replay.HasReceivedAllFrames = true; - // create a new handler with the replay for the frames to be sorted. + // create a new handler with the replay for the sort to be performed. handler = new TestInputHandler(replay); // ensure sort stability by checking whether the "data" assigned to each time-repeated frame is in ascending order, as it was before sort. From 45c0b74151c7f547838a98786c5859194ad3f442 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 4 May 2021 23:41:46 +0300 Subject: [PATCH 376/400] Use LINQ select for data assigning for simplicity To avoid having to read through all of frames and ensure nothing is failing there --- .../NonVisual/FramedReplayInputHandlerTest.cs | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index fe1186bf95..a9f9dfdc83 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -284,31 +284,33 @@ namespace osu.Game.Tests.NonVisual { const double repeating_time = 5000; + int incrementingData = 0; + // add a range of frames randomized in time but have a "data" assigned to them in ascending order. replay.Frames.AddRange(new[] { - new TestReplayFrame(repeating_time, true, 0), - new TestReplayFrame(0, true, 1), - new TestReplayFrame(3000, true, 2), - new TestReplayFrame(repeating_time, true, 3), - new TestReplayFrame(repeating_time, true, 4), - new TestReplayFrame(6000, true, 5), - new TestReplayFrame(9000, true, 6), - new TestReplayFrame(repeating_time, true, 7), - new TestReplayFrame(repeating_time, true, 8), - new TestReplayFrame(1000, true, 9), - new TestReplayFrame(11000, true, 10), - new TestReplayFrame(21000, true, 11), - new TestReplayFrame(4000, true, 12), - new TestReplayFrame(repeating_time, true, 13), - new TestReplayFrame(repeating_time, true, 14), - new TestReplayFrame(8000, true, 15), - new TestReplayFrame(2000, true, 16), - new TestReplayFrame(7000, true, 17), - new TestReplayFrame(repeating_time, true, 18), - new TestReplayFrame(repeating_time, true, 19), - new TestReplayFrame(10000, true, 20), - }); + new TestReplayFrame(repeating_time, true), + new TestReplayFrame(0, true), + new TestReplayFrame(3000, true), + new TestReplayFrame(repeating_time, true), + new TestReplayFrame(repeating_time, true), + new TestReplayFrame(6000, true), + new TestReplayFrame(9000, true), + new TestReplayFrame(repeating_time, true), + new TestReplayFrame(repeating_time, true), + new TestReplayFrame(1000, true), + new TestReplayFrame(11000, true), + new TestReplayFrame(21000, true), + new TestReplayFrame(4000, true), + new TestReplayFrame(repeating_time, true), + new TestReplayFrame(repeating_time, true), + new TestReplayFrame(8000, true), + new TestReplayFrame(2000, true), + new TestReplayFrame(7000, true), + new TestReplayFrame(repeating_time, true), + new TestReplayFrame(repeating_time, true), + new TestReplayFrame(10000, true), + }.Select(f => new TestReplayFrame(f.Time, true, incrementingData++))); replay.HasReceivedAllFrames = true; From 973475823735a94dce077f2b5e9005df9e689a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 May 2021 22:48:57 +0200 Subject: [PATCH 377/400] Simplify test case further --- .../NonVisual/FramedReplayInputHandlerTest.cs | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index a9f9dfdc83..a4fe2172e1 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -284,33 +284,31 @@ namespace osu.Game.Tests.NonVisual { const double repeating_time = 5000; - int incrementingData = 0; - // add a range of frames randomized in time but have a "data" assigned to them in ascending order. replay.Frames.AddRange(new[] { - new TestReplayFrame(repeating_time, true), - new TestReplayFrame(0, true), - new TestReplayFrame(3000, true), - new TestReplayFrame(repeating_time, true), - new TestReplayFrame(repeating_time, true), - new TestReplayFrame(6000, true), - new TestReplayFrame(9000, true), - new TestReplayFrame(repeating_time, true), - new TestReplayFrame(repeating_time, true), - new TestReplayFrame(1000, true), - new TestReplayFrame(11000, true), - new TestReplayFrame(21000, true), - new TestReplayFrame(4000, true), - new TestReplayFrame(repeating_time, true), - new TestReplayFrame(repeating_time, true), - new TestReplayFrame(8000, true), - new TestReplayFrame(2000, true), - new TestReplayFrame(7000, true), - new TestReplayFrame(repeating_time, true), - new TestReplayFrame(repeating_time, true), - new TestReplayFrame(10000, true), - }.Select(f => new TestReplayFrame(f.Time, true, incrementingData++))); + repeating_time, + 0, + 3000, + repeating_time, + repeating_time, + 6000, + 9000, + repeating_time, + repeating_time, + 1000, + 11000, + 21000, + 4000, + repeating_time, + repeating_time, + 8000, + 2000, + 7000, + repeating_time, + repeating_time, + 10000 + }.Select((time, index) => new TestReplayFrame(time, true, index))); replay.HasReceivedAllFrames = true; @@ -321,7 +319,7 @@ namespace osu.Game.Tests.NonVisual var repeatingTimeFramesData = replay.Frames .Cast() .Where(f => f.Time == repeating_time) - .Select(f => f.Data); + .Select(f => f.FrameIndex); Assert.That(repeatingTimeFramesData, Is.Ordered.Ascending); } @@ -372,13 +370,13 @@ namespace osu.Game.Tests.NonVisual private class TestReplayFrame : ReplayFrame { public readonly bool IsImportant; - public readonly int Data; + public readonly int FrameIndex; - public TestReplayFrame(double time, bool isImportant = false, int data = 0) + public TestReplayFrame(double time, bool isImportant = false, int frameIndex = 0) : base(time) { IsImportant = isImportant; - Data = data; + FrameIndex = frameIndex; } } From f7d9fb094e8d764e01948a6a8104e6a5f8adc2d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 May 2021 22:59:10 +0200 Subject: [PATCH 378/400] Reword & clarify comments --- osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index a4fe2172e1..407dec936b 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -284,7 +284,10 @@ namespace osu.Game.Tests.NonVisual { const double repeating_time = 5000; - // add a range of frames randomized in time but have a "data" assigned to them in ascending order. + // add a collection of frames in shuffled order time-wise; each frame also stores its original index to check stability later. + // data is hand-picked and breaks if the unstable List.Sort() is used. + // in theory this can still return a false-positive with another unstable algorithm if extremely unlucky, + // but there is no conceivable fool-proof way to prevent that anyways. replay.Frames.AddRange(new[] { repeating_time, @@ -315,7 +318,7 @@ namespace osu.Game.Tests.NonVisual // create a new handler with the replay for the sort to be performed. handler = new TestInputHandler(replay); - // ensure sort stability by checking whether the "data" assigned to each time-repeated frame is in ascending order, as it was before sort. + // ensure sort stability by checking that the frames with time == repeating_time are sorted in ascending frame index order themselves. var repeatingTimeFramesData = replay.Frames .Cast() .Where(f => f.Time == repeating_time) From 23b9d8c260058df9da31790b981ba50504b8aa5a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 4 May 2021 14:02:12 -0700 Subject: [PATCH 379/400] Fix alpha not being zero when string is set to empty and use inequality on supporter condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Settings/Sections/UserInterface/MainMenuSettings.cs | 2 +- osu.Game/Overlays/Settings/SettingsItem.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index 707f8cd314..b5ab6d2f60 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface { const string not_supporter_note = "Changes to this setting will only apply with an active osu!supporter tag."; - backgroundSourceDropdown.NoteText = user.Value?.IsSupporter == false ? not_supporter_note : string.Empty; + backgroundSourceDropdown.NoteText = user.Value?.IsSupporter != true ? not_supporter_note : string.Empty; }, true); } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 6405431f6b..c6f17cfc23 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings { set { - noteText.Alpha = 1; + noteText.Alpha = string.IsNullOrWhiteSpace(value) ? 0 : 1; noteText.Text = value; } } From 1472960319ecdd6f4627bdfd7aff9896b6bdcfe1 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Tue, 4 May 2021 21:35:36 -0400 Subject: [PATCH 380/400] Hide and disable skip outro overlay on rewind --- osu.Game/Screens/Play/Player.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cf26bc479a..88e617245b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -598,6 +598,7 @@ namespace osu.Game.Screens.Play completionProgressDelegate?.Cancel(); completionProgressDelegate = null; ValidForResume = true; + skipOutroOverlay.Hide(); return; } From 1d4a8bc0ae9df6ea3b796974953c6b903ddb1f5c Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Tue, 4 May 2021 22:23:36 -0400 Subject: [PATCH 381/400] Add visual test for rewinding --- .../Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 8326063f81..3229716b7e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -116,6 +116,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for score shown", () => Player.IsScoreShown); } + [Test] + public void TestStoryboardRewind() + { + CreateTest(null); + AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + AddStep("rewind", () => Player.GameplayClockContainer.Seek(-1000)); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); + } + protected override bool AllowFail => true; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); From 6178f38c95277c91e2a07cb0766d9a33318d24aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 May 2021 16:15:06 +0900 Subject: [PATCH 382/400] Reword unlimited frame rate warning a bit --- .../Overlays/Settings/Sections/Graphics/RendererSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 3ad201640d..c42db1706f 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics frameLimiterDropdown.Current.BindValueChanged(limit => { - const string unlimited_frames_note = "Using unlimited frame limiter can lead to stutters. \"2x refresh rate\" is recommended."; + const string unlimited_frames_note = "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. \"2x refresh rate\" is recommended."; frameLimiterDropdown.NoteText = limit.NewValue == FrameSync.Unlimited ? unlimited_frames_note : string.Empty; }, true); From 1288f69fada2d8457b2e21bd9341ff517ab72c58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 May 2021 16:16:02 +0900 Subject: [PATCH 383/400] Rename to `WarningText` --- .../Settings/Sections/Graphics/LayoutSettings.cs | 2 +- .../Settings/Sections/Graphics/RendererSettings.cs | 2 +- .../Sections/UserInterface/MainMenuSettings.cs | 2 +- osu.Game/Overlays/Settings/SettingsItem.cs | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index a24dd1f64b..937bcc8abf 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -147,7 +147,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics const string not_fullscreen_note = "Running without fullscreen mode will increase your input latency!"; - windowModeDropdown.NoteText = mode.NewValue != WindowMode.Fullscreen ? not_fullscreen_note : string.Empty; + windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? not_fullscreen_note : string.Empty; }, true); windowModes.BindCollectionChanged((sender, args) => diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index c42db1706f..70225ff6b8 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { const string unlimited_frames_note = "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. \"2x refresh rate\" is recommended."; - frameLimiterDropdown.NoteText = limit.NewValue == FrameSync.Unlimited ? unlimited_frames_note : string.Empty; + frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? unlimited_frames_note : string.Empty; }, true); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index b5ab6d2f60..7c4c88f344 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface { const string not_supporter_note = "Changes to this setting will only apply with an active osu!supporter tag."; - backgroundSourceDropdown.NoteText = user.Value?.IsSupporter != true ? not_supporter_note : string.Empty; + backgroundSourceDropdown.WarningText = user.Value?.IsSupporter != true ? not_supporter_note : string.Empty; }, true); } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index c6f17cfc23..f4d7c72b7f 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Settings private SpriteText labelText; - private readonly OsuTextFlowContainer noteText; + private readonly OsuTextFlowContainer warningText; public bool ShowsDefaultIndicator = true; @@ -61,14 +61,14 @@ namespace osu.Game.Overlays.Settings /// /// Text to be displayed at the bottom of this . - /// Used for further explanation or indicating drawbacks of the current setting. + /// Generally used to recommend the user change their setting as the current one is considered sub-optimal. /// - public string NoteText + public string WarningText { set { - noteText.Alpha = string.IsNullOrWhiteSpace(value) ? 0 : 1; - noteText.Text = value; + warningText.Alpha = string.IsNullOrWhiteSpace(value) ? 0 : 1; + warningText.Text = value; } } @@ -110,7 +110,7 @@ namespace osu.Game.Overlays.Settings Children = new[] { Control = CreateControl(), - noteText = new OsuTextFlowContainer + warningText = new OsuTextFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Settings [BackgroundDependencyLoader] private void load(OsuColour colours) { - noteText.Colour = colours.Yellow; + warningText.Colour = colours.Yellow; } private void updateDisabled() From 19ffcd00c27d04ce184f16cd55f141c803dfd011 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 May 2021 16:19:06 +0900 Subject: [PATCH 384/400] Initialise warning text flow lazily as most items will not use it --- osu.Game/Overlays/Settings/SettingsItem.cs | 29 ++++++++++++---------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index f4d7c72b7f..09e458ad7e 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -36,12 +36,15 @@ namespace osu.Game.Overlays.Settings private SpriteText labelText; - private readonly OsuTextFlowContainer warningText; + private OsuTextFlowContainer warningText; public bool ShowsDefaultIndicator = true; public string TooltipText { get; set; } + [Resolved] + private OsuColour colours { get; set; } + public virtual LocalisableString LabelText { get => labelText?.Text ?? string.Empty; @@ -67,6 +70,18 @@ namespace osu.Game.Overlays.Settings { set { + if (warningText == null) + { + // construct lazily for cases where the label is not needed (may be provided by the Control). + FlowContent.Add(warningText = new OsuTextFlowContainer + { + Colour = colours.Yellow, + Margin = new MarginPadding { Bottom = 5 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }); + } + warningText.Alpha = string.IsNullOrWhiteSpace(value) ? 0 : 1; warningText.Text = value; } @@ -110,12 +125,6 @@ namespace osu.Game.Overlays.Settings Children = new[] { Control = CreateControl(), - warningText = new OsuTextFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - }, }, }, }; @@ -132,12 +141,6 @@ namespace osu.Game.Overlays.Settings } } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - warningText.Colour = colours.Yellow; - } - private void updateDisabled() { if (labelText != null) From 08a45e9fc29d37b686a8430db7c37aab39894c00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 May 2021 16:26:34 +0900 Subject: [PATCH 385/400] Remove dead code --- osu.Game/Overlays/Settings/SettingsItem.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 09e458ad7e..86a836d29b 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -230,12 +230,6 @@ namespace osu.Game.Overlays.Settings UpdateState(); } - public void SetButtonColour(Color4 buttonColour) - { - this.buttonColour = buttonColour; - UpdateState(); - } - public void UpdateState() => Scheduler.AddOnce(updateState); private void updateState() From 3cc9bad97968f220fc475f657f0189eb524b3f8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 May 2021 17:49:33 +0900 Subject: [PATCH 386/400] Actually check for correct state of fade content in rewind test --- .../Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 3229716b7e..4138a81ebd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Graphics; @@ -119,9 +120,16 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardRewind() { + SkipOverlay.FadeContainer fadeContainer() => Player.ChildrenOfType().First(); + CreateTest(null); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible); + AddStep("rewind", () => Player.GameplayClockContainer.Seek(-1000)); + AddUntilStep("skip overlay content not visible", () => fadeContainer().State == Visibility.Hidden); + + AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); } From 9ec3255c505e99a826b3eba40f86f80829502562 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 May 2021 17:50:25 +0900 Subject: [PATCH 387/400] Fix `SkipOverlay`'s `FadeContent` not getting correct state from parent --- osu.Game/Screens/Play/SkipOverlay.cs | 30 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index ddb78dfb67..ed49fc40b2 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -8,19 +8,19 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; using osu.Game.Screens.Ranking; using osuTK; using osuTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Containers; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play { @@ -92,6 +92,18 @@ namespace osu.Game.Screens.Play private double fadeOutBeginTime => startTime - MasterGameplayClockContainer.MINIMUM_SKIP_TIME; + public override void Hide() + { + base.Hide(); + fadeContainer.Hide(); + } + + public override void Show() + { + base.Show(); + fadeContainer.Show(); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -147,7 +159,7 @@ namespace osu.Game.Screens.Play { } - private class FadeContainer : Container, IStateful + public class FadeContainer : Container, IStateful { public event Action StateChanged; @@ -170,7 +182,7 @@ namespace osu.Game.Screens.Play switch (state) { case Visibility.Visible: - // we may be triggered to become visible mnultiple times but we only want to transform once. + // we may be triggered to become visible multiple times but we only want to transform once. if (stateChanged) this.FadeIn(500, Easing.OutExpo); From 377af38d94541c48cab0d7083cae089b28916f20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 May 2021 17:59:49 +0900 Subject: [PATCH 388/400] Remove unnecessary pixelSnapping parameter This was only required because there was text being rendered to the `BufferedContainer` content until now. Removing this should allow for better resolution in the background display (due to using a better minify scale mode). --- osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs index 566f49a799..0233112c69 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both; - InternalChild = new BufferedContainer(pixelSnapping: true) + InternalChild = new BufferedContainer() { CacheDrawnFrameBuffer = true, RelativeSizeAxes = Axes.Both, From 1410b8f36dce587d91b030bfe5b4736828bb074b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 May 2021 18:30:57 +0900 Subject: [PATCH 389/400] Fix follow points displaying at incorrect locations when dragging a slider out-of-bounds --- .../Objects/Drawables/Connections/FollowPointConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 5541d0e790..cda4715280 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Entry = null; } - private void onEntryInvalidated() => refreshPoints(); + private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints); private void refreshPoints() { From cf6ed7a7cf5bac2d8d8d463a1672b7318bd0b3d1 Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 5 May 2021 13:13:37 +0200 Subject: [PATCH 390/400] Refactored out changes in StarRatingDisplay --- .../Ranking/Expanded/StarRatingDisplay.cs | 145 +++++++----------- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 68 ++++---- .../Select/BeatmapInfoWedgeBackground.cs | 2 +- 3 files changed, 97 insertions(+), 118 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 748f58e430..f7e50fdc8a 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -22,22 +22,7 @@ namespace osu.Game.Screens.Ranking.Expanded /// public class StarRatingDisplay : CompositeDrawable { - [Resolved] - private OsuColour colours { get; set; } - - private CircularContainer colorContainer; - private StarDifficulty starDifficulty; - private FillFlowContainer foregroundContainer; - - public StarDifficulty StarDifficulty - { - get => starDifficulty; - set - { - starDifficulty = value; - setDifficulty(starDifficulty); - } - } + private readonly StarDifficulty difficulty; /// /// Creates a new using an already computed . @@ -45,94 +30,78 @@ namespace osu.Game.Screens.Ranking.Expanded /// The already computed to display the star difficulty of. public StarRatingDisplay(StarDifficulty starDifficulty) { - this.starDifficulty = starDifficulty; - } - - private void setDifficulty(StarDifficulty difficulty) - { - colorContainer.FadeColour(getDifficultyColour(difficulty), 250); - - foregroundContainer.Expire(); - foregroundContainer = null; - AddInternal(foregroundContainer = createForegroundContainer(difficulty)); + difficulty = starDifficulty; } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) { AutoSizeAxes = Axes.Both; - InternalChildren = new Drawable[] - { - colorContainer = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Colour = getDifficultyColour(starDifficulty), - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - }, - } - }, - foregroundContainer = createForegroundContainer(starDifficulty), - }; - } - - private ColourInfo getDifficultyColour(StarDifficulty difficulty) - { - return difficulty.DifficultyRating == DifficultyRating.ExpertPlus - ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); - } - - private FillFlowContainer createForegroundContainer(StarDifficulty difficulty) - { var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); string wholePart = starRatingParts[0]; string fractionPart = starRatingParts[1]; string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - return new FillFlowContainer + ColourInfo backgroundColour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus + ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) + : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); + + InternalChildren = new Drawable[] { - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 8, Vertical = 4 }, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(2, 0), - Children = new Drawable[] + new CircularContainer { - new SpriteIcon + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(7), - Icon = FontAwesome.Solid.Star, - Colour = Color4.Black - }, - new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - TextAnchor = Anchor.BottomLeft, - }.With(t => - { - t.AddText($"{wholePart}", s => + new Box { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 14); - s.UseFullGlyphHeight = false; - }); - t.AddText($"{separator}{fractionPart}", s => + RelativeSizeAxes = Axes.Both, + Colour = backgroundColour + }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 8, Vertical = 4 }, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2, 0), + Children = new Drawable[] + { + new SpriteIcon { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 7); - s.UseFullGlyphHeight = false; - }); - }), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(7), + Icon = FontAwesome.Solid.Star, + Colour = Color4.Black + }, + new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + TextAnchor = Anchor.BottomLeft, + }.With(t => + { + t.AddText($"{wholePart}", s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 14); + s.UseFullGlyphHeight = false; + }); + + t.AddText($"{separator}{fractionPart}", s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 7); + s.UseFullGlyphHeight = false; + }); + }) + } } }; } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index cb5a276a5d..04063d5819 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Select private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; private FillFlowContainer infoLabelContainer; - private StarRatingDisplay starRatingDisplay; + private Container topRightMetadataContainer; private Container bpmLabelContainer; private ModSettingChangeTracker settingChangeTracker; private CancellationTokenSource cancellationTokenSource; @@ -232,34 +232,15 @@ namespace osu.Game.Screens.Select }, } }, - new FillFlowContainer + topRightMetadataContainer = new Container { Name = "Topright-aligned metadata", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Direction = FillDirection.Vertical, Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, AutoSizeAxes = Axes.Both, Shear = wedged_container_shear, - Children = new Drawable[] - { - starRatingDisplay = new StarRatingDisplay(starDifficulty.Value ?? new StarDifficulty()) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Shear = -wedged_container_shear, - Margin = new MarginPadding { Bottom = 5 } - }, - StatusPill = new BeatmapSetOnlineStatusPill - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Shear = -wedged_container_shear, - TextSize = 11, - TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, - Status = beatmapInfo.Status, - } - } + Child = createTopRightMetadataContainer(beatmapInfo, starDifficulty.Value ?? new StarDifficulty()) }, new FillFlowContainer { @@ -306,21 +287,50 @@ namespace osu.Game.Screens.Select titleBinding.BindValueChanged(_ => setMetadata(metadata.Source)); artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true); - starDifficulty.BindValueChanged(updateStarRatingDisplay, true); + starDifficulty.BindValueChanged(updateTopRightMetadata, true); // no difficulty means it can't have a status to show if (beatmapInfo.Version == null) StatusPill.Hide(); } - private void updateStarRatingDisplay(ValueChangedEvent valueChanged) + private void updateTopRightMetadata(ValueChangedEvent valueChanged) { - if (valueChanged.NewValue.HasValue && valueChanged.NewValue.Value.Stars > 0) - starRatingDisplay.Show(); - else - starRatingDisplay.Hide(); + topRightMetadataContainer.Child.FadeOut(250); + topRightMetadataContainer.Child.Expire(); + topRightMetadataContainer.Child = createTopRightMetadataContainer(beatmap.BeatmapInfo, valueChanged.NewValue ?? new StarDifficulty()); + } - starRatingDisplay.StarDifficulty = valueChanged.NewValue ?? new StarDifficulty(); + private FillFlowContainer createTopRightMetadataContainer(BeatmapInfo beatmapInfo, StarDifficulty difficulty) + { + var container = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + }; + + if (difficulty.Stars > 0) + { + container.Add(new StarRatingDisplay(difficulty) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Shear = -wedged_container_shear, + Margin = new MarginPadding { Bottom = 5 } + }); + } + + container.Add(StatusPill = new BeatmapSetOnlineStatusPill + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Shear = -wedged_container_shear, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Status = beatmapInfo.Status, + }); + + return container; } private void refreshModInformation(ValueChangedEvent> modsChangedEvent) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs index 0233112c69..f50fb4dc8a 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both; - InternalChild = new BufferedContainer() + InternalChild = new BufferedContainer { CacheDrawnFrameBuffer = true, RelativeSizeAxes = Axes.Both, From 4ef901d08d51e586e447f831821463150d14229c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 May 2021 21:07:49 +0900 Subject: [PATCH 391/400] Remove unnecessary redirection property to `Container.Info` --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 4 ++-- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 688cc9a035..ec19f00087 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -193,9 +193,9 @@ namespace osu.Game.Tests.Visual.SongSelect private class TestBeatmapInfoWedge : BeatmapInfoWedge { - public new WedgeInfoText Info => base.Info; - public new BeatmapInfoWedgeContainer Container => base.Container; + + public WedgeInfoText Info => base.Container.Info; } private class TestHitObject : ConvertHitObject, IHasPosition diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 04063d5819..c86bdc99ff 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -41,7 +41,6 @@ namespace osu.Game.Screens.Select private IBindable ruleset { get; set; } protected BeatmapInfoWedgeContainer Container; - protected WedgeInfoText Info => Container.Info; public BeatmapInfoWedge() { From 5049e2fbf9bf709df1f4b2614ef80c11e8f5d31e Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 5 May 2021 15:11:38 +0200 Subject: [PATCH 392/400] Refactored out changes in DifficultyColourBar --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 66 +++++++++------------ 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index c86bdc99ff..448bf088dc 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -174,6 +174,7 @@ namespace osu.Game.Screens.Select private FillFlowContainer infoLabelContainer; private Container topRightMetadataContainer; private Container bpmLabelContainer; + private Container difficultyColourBarContainer; private ModSettingChangeTracker settingChangeTracker; private CancellationTokenSource cancellationTokenSource; private IBindable starDifficulty; @@ -206,10 +207,11 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { - new DifficultyColourBar(beatmapInfo) + difficultyColourBarContainer = new Container { RelativeSizeAxes = Axes.Y, Width = 20, + Child = createDifficultyColourBar(starDifficulty.Value ?? new StarDifficulty()), }, new FillFlowContainer { @@ -286,20 +288,32 @@ namespace osu.Game.Screens.Select titleBinding.BindValueChanged(_ => setMetadata(metadata.Source)); artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true); - starDifficulty.BindValueChanged(updateTopRightMetadata, true); + starDifficulty.BindValueChanged(updateDifficulty, true); // no difficulty means it can't have a status to show if (beatmapInfo.Version == null) StatusPill.Hide(); } - private void updateTopRightMetadata(ValueChangedEvent valueChanged) + private void updateDifficulty(ValueChangedEvent valueChanged) { + var difficulty = valueChanged.NewValue ?? new StarDifficulty(); + topRightMetadataContainer.Child.FadeOut(250); topRightMetadataContainer.Child.Expire(); - topRightMetadataContainer.Child = createTopRightMetadataContainer(beatmap.BeatmapInfo, valueChanged.NewValue ?? new StarDifficulty()); + topRightMetadataContainer.Child = createTopRightMetadataContainer(beatmap.BeatmapInfo, difficulty); + + difficultyColourBarContainer.Child.Expire(); + difficultyColourBarContainer.Child = createDifficultyColourBar(difficulty); } + private DifficultyColourBar createDifficultyColourBar(StarDifficulty difficulty) + => new DifficultyColourBar(difficulty) + { + RelativeSizeAxes = Axes.Y, + Width = 20, + }; + private FillFlowContainer createTopRightMetadataContainer(BeatmapInfo beatmapInfo, StarDifficulty difficulty) { var container = new FillFlowContainer @@ -515,64 +529,38 @@ namespace osu.Game.Screens.Select private class DifficultyColourBar : Container { - [Resolved] - private OsuColour colours { get; set; } + private readonly StarDifficulty difficulty; - private Box solidDifficultyBox; - private Box transparentDifficultyBox; - private CancellationTokenSource cancellationTokenSource; - private IBindable starDifficulty; - - private readonly BeatmapInfo beatmapInfo; - - public DifficultyColourBar(BeatmapInfo beatmapInfo) + public DifficultyColourBar(StarDifficulty difficulty) { - this.beatmapInfo = beatmapInfo; + this.difficulty = difficulty; } [BackgroundDependencyLoader] - private void load(BeatmapDifficultyCache difficultyCache) + private void load(OsuColour colours) { const float full_opacity_ratio = 0.7f; - cancellationTokenSource?.Cancel(); - cancellationTokenSource = new CancellationTokenSource(); - - starDifficulty?.UnbindAll(); - starDifficulty = difficultyCache.GetBindableDifficulty(beatmapInfo, cancellationTokenSource.Token); + var difficultyColour = colours.ForDifficultyRating(difficulty.DifficultyRating); Children = new Drawable[] { - solidDifficultyBox = new Box + new Box { RelativeSizeAxes = Axes.Both, + Colour = difficultyColour, Width = full_opacity_ratio, }, - transparentDifficultyBox = new Box + new Box { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, + Colour = difficultyColour, Alpha = 0.5f, X = full_opacity_ratio, Width = 1 - full_opacity_ratio, } }; - - starDifficulty.BindValueChanged(setColour, true); - } - - private void setColour(ValueChangedEvent valueChanged) - { - var difficultyColour = colours.ForDifficultyRating(valueChanged.NewValue?.DifficultyRating ?? (new StarDifficulty()).DifficultyRating); - - solidDifficultyBox.Colour = difficultyColour; - transparentDifficultyBox.Colour = difficultyColour; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - cancellationTokenSource?.Cancel(); } } } From 88506a51dd95ec070457889ad6c89b96ca777720 Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 5 May 2021 17:51:29 +0200 Subject: [PATCH 393/400] reduced complexity --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 75 ++++++++++----------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 448bf088dc..475a547c27 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -233,15 +233,35 @@ namespace osu.Game.Screens.Select }, } }, - topRightMetadataContainer = new Container + new FillFlowContainer { Name = "Topright-aligned metadata", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Direction = FillDirection.Vertical, Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, AutoSizeAxes = Axes.Both, Shear = wedged_container_shear, - Child = createTopRightMetadataContainer(beatmapInfo, starDifficulty.Value ?? new StarDifficulty()) + Children = new Drawable[] + { + starRatingContainer = new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Shear = -wedged_container_shear, + Margin = new MarginPadding { Bottom = 5 } + }, + StatusPill = new BeatmapSetOnlineStatusPill + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Shear = -wedged_container_shear, + TextSize = 11, + TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, + Status = beatmapInfo.Status, + } + } }, new FillFlowContainer { @@ -299,51 +319,24 @@ namespace osu.Game.Screens.Select { var difficulty = valueChanged.NewValue ?? new StarDifficulty(); - topRightMetadataContainer.Child.FadeOut(250); - topRightMetadataContainer.Child.Expire(); - topRightMetadataContainer.Child = createTopRightMetadataContainer(beatmap.BeatmapInfo, difficulty); + if (starRatingContainer.Children.Count > 0) + { + starRatingContainer.Child.FadeOut(250); + starRatingContainer.Child.Expire(); + } - difficultyColourBarContainer.Child.Expire(); - difficultyColourBarContainer.Child = createDifficultyColourBar(difficulty); - } + starRatingContainer.Child = difficulty.Stars > 0 ? new StarRatingDisplay(difficulty) : Empty(); - private DifficultyColourBar createDifficultyColourBar(StarDifficulty difficulty) - => new DifficultyColourBar(difficulty) + if (difficultyColourBarContainer.Children.Count > 0) + { + difficultyColourBarContainer.Child.Expire(); + } + + difficultyColourBarContainer.Child = new DifficultyColourBar(difficulty) { RelativeSizeAxes = Axes.Y, Width = 20, }; - - private FillFlowContainer createTopRightMetadataContainer(BeatmapInfo beatmapInfo, StarDifficulty difficulty) - { - var container = new FillFlowContainer - { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - }; - - if (difficulty.Stars > 0) - { - container.Add(new StarRatingDisplay(difficulty) - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Shear = -wedged_container_shear, - Margin = new MarginPadding { Bottom = 5 } - }); - } - - container.Add(StatusPill = new BeatmapSetOnlineStatusPill - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Shear = -wedged_container_shear, - TextSize = 11, - TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, - Status = beatmapInfo.Status, - }); - - return container; } private void refreshModInformation(ValueChangedEvent> modsChangedEvent) From 279750775848a7f6536ecbff54e69773d01c2464 Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 5 May 2021 17:56:07 +0200 Subject: [PATCH 394/400] Reorganized elements for readability --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 128 ++++++++++---------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 475a547c27..15e484e24c 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -132,39 +132,13 @@ namespace osu.Game.Screens.Select } } - public class BeatmapInfoWedgeContainer : Container - { - private readonly WorkingBeatmap beatmap; - private readonly RulesetInfo ruleset; - - internal WedgeInfoText Info; - - public BeatmapInfoWedgeContainer(WorkingBeatmap beatmap, RulesetInfo ruleset) - { - this.beatmap = beatmap; - this.ruleset = ruleset; - } - - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - new BeatmapInfoWedgeBackground(beatmap), - Info = new WedgeInfoText(beatmap, ruleset), - }; - } - } - public class WedgeInfoText : Container { - public FillFlowContainer MapperContainer { get; private set; } + public OsuSpriteText VersionLabel { get; private set; } public OsuSpriteText TitleLabel { get; private set; } public OsuSpriteText ArtistLabel { get; private set; } - public OsuSpriteText VersionLabel { get; private set; } public BeatmapSetOnlineStatusPill StatusPill { get; private set; } + public FillFlowContainer MapperContainer { get; private set; } [Resolved] private IBindable> mods { get; set; } @@ -172,16 +146,17 @@ namespace osu.Game.Screens.Select private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; private FillFlowContainer infoLabelContainer; - private Container topRightMetadataContainer; + private Container starRatingContainer; private Container bpmLabelContainer; private Container difficultyColourBarContainer; - private ModSettingChangeTracker settingChangeTracker; private CancellationTokenSource cancellationTokenSource; private IBindable starDifficulty; private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; + private ModSettingChangeTracker settingChangeTracker; + public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset) { this.beatmap = beatmap; @@ -211,7 +186,6 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Y, Width = 20, - Child = createDifficultyColourBar(starDifficulty.Value ?? new StarDifficulty()), }, new FillFlowContainer { @@ -304,8 +278,6 @@ namespace osu.Game.Screens.Select } }; - addInfoLabels(); - titleBinding.BindValueChanged(_ => setMetadata(metadata.Source)); artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true); starDifficulty.BindValueChanged(updateDifficulty, true); @@ -313,38 +285,8 @@ namespace osu.Game.Screens.Select // no difficulty means it can't have a status to show if (beatmapInfo.Version == null) StatusPill.Hide(); - } - private void updateDifficulty(ValueChangedEvent valueChanged) - { - var difficulty = valueChanged.NewValue ?? new StarDifficulty(); - - if (starRatingContainer.Children.Count > 0) - { - starRatingContainer.Child.FadeOut(250); - starRatingContainer.Child.Expire(); - } - - starRatingContainer.Child = difficulty.Stars > 0 ? new StarRatingDisplay(difficulty) : Empty(); - - if (difficultyColourBarContainer.Children.Count > 0) - { - difficultyColourBarContainer.Child.Expire(); - } - - difficultyColourBarContainer.Child = new DifficultyColourBar(difficulty) - { - RelativeSizeAxes = Axes.Y, - Width = 20, - }; - } - - private void refreshModInformation(ValueChangedEvent> modsChangedEvent) - { - settingChangeTracker?.Dispose(); - settingChangeTracker = new ModSettingChangeTracker(modsChangedEvent.NewValue); - settingChangeTracker.SettingChanged += _ => refreshBPMLabel(modsChangedEvent.NewValue); - refreshBPMLabel(modsChangedEvent.NewValue); + addInfoLabels(); } private void setMetadata(string source) @@ -455,6 +397,38 @@ namespace osu.Game.Screens.Select }; } + private void updateDifficulty(ValueChangedEvent valueChanged) + { + var difficulty = valueChanged.NewValue ?? new StarDifficulty(); + + if (starRatingContainer.Children.Count > 0) + { + starRatingContainer.Child.FadeOut(250); + starRatingContainer.Child.Expire(); + } + + starRatingContainer.Child = difficulty.Stars > 0 ? new StarRatingDisplay(difficulty) : Empty(); + + if (difficultyColourBarContainer.Children.Count > 0) + { + difficultyColourBarContainer.Child.Expire(); + } + + difficultyColourBarContainer.Child = new DifficultyColourBar(difficulty) + { + RelativeSizeAxes = Axes.Y, + Width = 20, + }; + } + + private void refreshModInformation(ValueChangedEvent> modsChangedEvent) + { + settingChangeTracker?.Dispose(); + settingChangeTracker = new ModSettingChangeTracker(modsChangedEvent.NewValue); + settingChangeTracker.SettingChanged += _ => refreshBPMLabel(modsChangedEvent.NewValue); + refreshBPMLabel(modsChangedEvent.NewValue); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -557,5 +531,31 @@ namespace osu.Game.Screens.Select } } } + + public class BeatmapInfoWedgeContainer : Container + { + private readonly WorkingBeatmap beatmap; + private readonly RulesetInfo ruleset; + + internal WedgeInfoText Info; + + public BeatmapInfoWedgeContainer(WorkingBeatmap beatmap, RulesetInfo ruleset) + { + this.beatmap = beatmap; + this.ruleset = ruleset; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new BeatmapInfoWedgeBackground(beatmap), + Info = new WedgeInfoText(beatmap, ruleset), + }; + } + } } } From bb385f425531c1efdf37fd258df9057edaf10fc9 Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 5 May 2021 18:15:59 +0200 Subject: [PATCH 395/400] Reverted difficulty and mod updates --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 117 +++++++++----------- 1 file changed, 55 insertions(+), 62 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 15e484e24c..4be7d3b0f4 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -40,6 +40,14 @@ namespace osu.Game.Screens.Select [Resolved] private IBindable ruleset { get; set; } + [Resolved] + private IBindable> mods { get; set; } + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + + private IBindable beatmapDifficulty; + protected BeatmapInfoWedgeContainer Container; public BeatmapInfoWedge() @@ -80,6 +88,8 @@ namespace osu.Game.Screens.Select private WorkingBeatmap beatmap; + private CancellationTokenSource cancellationSource; + public WorkingBeatmap Beatmap { get => beatmap; @@ -88,6 +98,13 @@ namespace osu.Game.Screens.Select if (beatmap == value) return; beatmap = value; + cancellationSource?.Cancel(); + cancellationSource = new CancellationTokenSource(); + + beatmapDifficulty?.UnbindAll(); + beatmapDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo, cancellationSource.Token); + beatmapDifficulty.BindValueChanged(_ => updateDisplay()); + updateDisplay(); } } @@ -117,7 +134,7 @@ namespace osu.Game.Screens.Select return; } - LoadComponentAsync(loadingInfo = new BeatmapInfoWedgeContainer(beatmap, ruleset.Value) + LoadComponentAsync(loadingInfo = new BeatmapInfoWedgeContainer(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty()) { Shear = -Shear, Depth = Container?.Depth + 1 ?? 0, @@ -132,6 +149,12 @@ namespace osu.Game.Screens.Select } } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + cancellationSource?.Cancel(); + } + public class WedgeInfoText : Container { public OsuSpriteText VersionLabel { get; private set; } @@ -140,41 +163,32 @@ namespace osu.Game.Screens.Select public BeatmapSetOnlineStatusPill StatusPill { get; private set; } public FillFlowContainer MapperContainer { get; private set; } - [Resolved] - private IBindable> mods { get; set; } - private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; private FillFlowContainer infoLabelContainer; - private Container starRatingContainer; private Container bpmLabelContainer; - private Container difficultyColourBarContainer; - private CancellationTokenSource cancellationTokenSource; - private IBindable starDifficulty; private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; + private readonly IReadOnlyList mods; + private readonly StarDifficulty starDifficulty; private ModSettingChangeTracker settingChangeTracker; - public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset) + public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList mods, StarDifficulty difficulty) { this.beatmap = beatmap; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; + this.mods = mods; + starDifficulty = difficulty; } [BackgroundDependencyLoader] - private void load(LocalisationManager localisation, BeatmapDifficultyCache difficultyCache) + private void load(LocalisationManager localisation) { var beatmapInfo = beatmap.BeatmapInfo; var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - cancellationTokenSource?.Cancel(); - cancellationTokenSource = new CancellationTokenSource(); - - starDifficulty?.UnbindAll(); - starDifficulty = difficultyCache.GetBindableDifficulty(beatmapInfo, cancellationTokenSource.Token); - RelativeSizeAxes = Axes.Both; titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); @@ -182,7 +196,7 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { - difficultyColourBarContainer = new Container + new DifficultyColourBar(starDifficulty) { RelativeSizeAxes = Axes.Y, Width = 20, @@ -216,16 +230,14 @@ namespace osu.Game.Screens.Select Padding = new MarginPadding { Top = 14, Right = shear_width / 2 }, AutoSizeAxes = Axes.Both, Shear = wedged_container_shear, - Children = new Drawable[] + Children = new[] { - starRatingContainer = new Container + createStarRatingDisplay(starDifficulty).With(display => { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Shear = -wedged_container_shear, - Margin = new MarginPadding { Bottom = 5 } - }, + display.Anchor = Anchor.TopRight; + display.Origin = Anchor.TopRight; + display.Shear = -wedged_container_shear; + }), StatusPill = new BeatmapSetOnlineStatusPill { Anchor = Anchor.TopRight, @@ -280,7 +292,6 @@ namespace osu.Game.Screens.Select titleBinding.BindValueChanged(_ => setMetadata(metadata.Source)); artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true); - starDifficulty.BindValueChanged(updateDifficulty, true); // no difficulty means it can't have a status to show if (beatmapInfo.Version == null) @@ -289,6 +300,13 @@ namespace osu.Game.Screens.Select addInfoLabels(); } + private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0 + ? new StarRatingDisplay(difficulty) + { + Margin = new MarginPadding { Bottom = 5 } + } + : Empty(); + private void setMetadata(string source) { ArtistLabel.Text = artistBinding.Value; @@ -320,7 +338,10 @@ namespace osu.Game.Screens.Select } }; - mods.BindValueChanged(refreshModInformation, true); + settingChangeTracker = new ModSettingChangeTracker(mods); + settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); + + refreshBPMLabel(); } private InfoLabel[] getRulesetInfoLabels() @@ -350,7 +371,7 @@ namespace osu.Game.Screens.Select return Array.Empty(); } - private void refreshBPMLabel(IReadOnlyList mods) + private void refreshBPMLabel() { var b = beatmap.Beatmap; if (b == null) @@ -397,38 +418,6 @@ namespace osu.Game.Screens.Select }; } - private void updateDifficulty(ValueChangedEvent valueChanged) - { - var difficulty = valueChanged.NewValue ?? new StarDifficulty(); - - if (starRatingContainer.Children.Count > 0) - { - starRatingContainer.Child.FadeOut(250); - starRatingContainer.Child.Expire(); - } - - starRatingContainer.Child = difficulty.Stars > 0 ? new StarRatingDisplay(difficulty) : Empty(); - - if (difficultyColourBarContainer.Children.Count > 0) - { - difficultyColourBarContainer.Child.Expire(); - } - - difficultyColourBarContainer.Child = new DifficultyColourBar(difficulty) - { - RelativeSizeAxes = Axes.Y, - Width = 20, - }; - } - - private void refreshModInformation(ValueChangedEvent> modsChangedEvent) - { - settingChangeTracker?.Dispose(); - settingChangeTracker = new ModSettingChangeTracker(modsChangedEvent.NewValue); - settingChangeTracker.SettingChanged += _ => refreshBPMLabel(modsChangedEvent.NewValue); - refreshBPMLabel(modsChangedEvent.NewValue); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -536,13 +525,17 @@ namespace osu.Game.Screens.Select { private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; + private readonly StarDifficulty starDifficulty; + private readonly IReadOnlyList mods; internal WedgeInfoText Info; - public BeatmapInfoWedgeContainer(WorkingBeatmap beatmap, RulesetInfo ruleset) + public BeatmapInfoWedgeContainer(WorkingBeatmap beatmap, RulesetInfo ruleset, IReadOnlyList mods, StarDifficulty difficulty) { this.beatmap = beatmap; this.ruleset = ruleset; + this.mods = mods; + starDifficulty = difficulty; } [BackgroundDependencyLoader] @@ -553,7 +546,7 @@ namespace osu.Game.Screens.Select Children = new Drawable[] { new BeatmapInfoWedgeBackground(beatmap), - Info = new WedgeInfoText(beatmap, ruleset), + Info = new WedgeInfoText(beatmap, ruleset, mods, starDifficulty), }; } } From b6b9a696017ca41dd9befb1ca5b63c2fa129f008 Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 5 May 2021 18:50:49 +0200 Subject: [PATCH 396/400] Removed unnecessary class for wrapping --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 7 +-- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 43 +++++-------------- 2 files changed, 14 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index ec19f00087..b9e92cba62 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; @@ -135,7 +136,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void selectBeatmap([CanBeNull] IBeatmap b) { - BeatmapInfoWedge.BeatmapInfoWedgeContainer containerBefore = null; + Container containerBefore = null; AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () => { @@ -193,9 +194,9 @@ namespace osu.Game.Tests.Visual.SongSelect private class TestBeatmapInfoWedge : BeatmapInfoWedge { - public new BeatmapInfoWedgeContainer Container => base.Container; + public new Container Container => base.Container; - public WedgeInfoText Info => base.Container.Info; + public new WedgeInfoText Info => base.Info; } private class TestHitObject : ConvertHitObject, IHasPosition diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 4be7d3b0f4..53ac97cc7d 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -48,7 +48,8 @@ namespace osu.Game.Screens.Select private IBindable beatmapDifficulty; - protected BeatmapInfoWedgeContainer Container; + protected Container Container; + protected WedgeInfoText Info; public BeatmapInfoWedge() { @@ -111,7 +112,7 @@ namespace osu.Game.Screens.Select public override bool IsPresent => base.IsPresent || Container == null; // Visibility is updated in the LoadComponentAsync callback - private BeatmapInfoWedgeContainer loadingInfo; + private Container loadingInfo; private void updateDisplay() { @@ -134,10 +135,16 @@ namespace osu.Game.Screens.Select return; } - LoadComponentAsync(loadingInfo = new BeatmapInfoWedgeContainer(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty()) + LoadComponentAsync(loadingInfo = new Container { + RelativeSizeAxes = Axes.Both, Shear = -Shear, Depth = Container?.Depth + 1 ?? 0, + Children = new Drawable[] + { + new BeatmapInfoWedgeBackground(beatmap), + Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty()), + } }, loaded => { // ensure we are the most recent loaded wedge. @@ -520,35 +527,5 @@ namespace osu.Game.Screens.Select } } } - - public class BeatmapInfoWedgeContainer : Container - { - private readonly WorkingBeatmap beatmap; - private readonly RulesetInfo ruleset; - private readonly StarDifficulty starDifficulty; - private readonly IReadOnlyList mods; - - internal WedgeInfoText Info; - - public BeatmapInfoWedgeContainer(WorkingBeatmap beatmap, RulesetInfo ruleset, IReadOnlyList mods, StarDifficulty difficulty) - { - this.beatmap = beatmap; - this.ruleset = ruleset; - this.mods = mods; - starDifficulty = difficulty; - } - - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] - { - new BeatmapInfoWedgeBackground(beatmap), - Info = new WedgeInfoText(beatmap, ruleset, mods, starDifficulty), - }; - } - } } } From fe9ade6754360eb1e36c41f46c7d386d9b8565c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 May 2021 02:14:04 +0900 Subject: [PATCH 397/400] Rename Container to DisplayedContent --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 6 +++--- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index b9e92cba62..67c85a1120 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -140,11 +140,11 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () => { - containerBefore = infoWedge.Container; + containerBefore = infoWedge.DisplayedContent; infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b); }); - AddUntilStep("wait for async load", () => infoWedge.Container != containerBefore); + AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore); } private IBeatmap createTestBeatmap(RulesetInfo ruleset) @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.SongSelect private class TestBeatmapInfoWedge : BeatmapInfoWedge { - public new Container Container => base.Container; + public new Container DisplayedContent => base.DisplayedContent; public new WedgeInfoText Info => base.Info; } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 53ac97cc7d..d84052b94d 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -48,7 +48,8 @@ namespace osu.Game.Screens.Select private IBindable beatmapDifficulty; - protected Container Container; + protected Container DisplayedContent; + protected WedgeInfoText Info; public BeatmapInfoWedge() @@ -110,7 +111,7 @@ namespace osu.Game.Screens.Select } } - public override bool IsPresent => base.IsPresent || Container == null; // Visibility is updated in the LoadComponentAsync callback + public override bool IsPresent => base.IsPresent || DisplayedContent == null; // Visibility is updated in the LoadComponentAsync callback private Container loadingInfo; @@ -124,9 +125,9 @@ namespace osu.Game.Screens.Select { State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; - Container?.FadeOut(250); - Container?.Expire(); - Container = null; + DisplayedContent?.FadeOut(250); + DisplayedContent?.Expire(); + DisplayedContent = null; } if (beatmap == null) @@ -139,7 +140,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Shear = -Shear, - Depth = Container?.Depth + 1 ?? 0, + Depth = DisplayedContent?.Depth + 1 ?? 0, Children = new Drawable[] { new BeatmapInfoWedgeBackground(beatmap), @@ -151,7 +152,7 @@ namespace osu.Game.Screens.Select if (loaded != loadingInfo) return; removeOldInfo(); - Add(Container = loaded); + Add(DisplayedContent = loaded); }); } } From cffeb8641f484a860580bbcce38f977563e68674 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 May 2021 02:14:57 +0900 Subject: [PATCH 398/400] Make setters private for protected containers --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index d84052b94d..18615d9192 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -48,9 +48,9 @@ namespace osu.Game.Screens.Select private IBindable beatmapDifficulty; - protected Container DisplayedContent; + protected Container DisplayedContent { get; private set; } - protected WedgeInfoText Info; + protected WedgeInfoText Info { get; private set; } public BeatmapInfoWedge() { From d4658c609b61d3f15cccbe840023bb6003efc91c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 5 May 2021 22:43:16 -0700 Subject: [PATCH 399/400] Fix warning text of bg source setting not being updated when user with supporter signs in/out --- .../Settings/Sections/UserInterface/MainMenuSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index 7c4c88f344..5f703ed5a4 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -57,11 +57,11 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface { base.LoadComplete(); - backgroundSourceDropdown.Current.BindValueChanged(source => + user.BindValueChanged(u => { const string not_supporter_note = "Changes to this setting will only apply with an active osu!supporter tag."; - backgroundSourceDropdown.WarningText = user.Value?.IsSupporter != true ? not_supporter_note : string.Empty; + backgroundSourceDropdown.WarningText = u.NewValue?.IsSupporter != true ? not_supporter_note : string.Empty; }, true); } } From af75c9ac82ab73f43188284eef35a67b7bf1dbfd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 May 2021 16:08:28 +0900 Subject: [PATCH 400/400] 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 5aee9e15cc..99cda7693d 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 1e0eabfff7..e448972066 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index e26e727e69..43ed2d7dc8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - +