From 6fe1b68510397ccccaf4339d2974784864ad02bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 22:30:40 +0900 Subject: [PATCH 001/708] Add a way to retrieve new WorkingBeatmap instances --- osu.Game/Beatmaps/BeatmapManager.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b4ea898b7d..69b513d256 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -281,8 +281,9 @@ namespace osu.Game.Beatmaps /// /// The beatmap to lookup. /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. May be returned if beatmapInfo requested matches + /// Whether to bypass the cache and return a new instance. /// A instance correlating to the provided . - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null, bool bypassCache = false) { if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) return previous; @@ -301,9 +302,14 @@ namespace osu.Game.Beatmaps lock (workingCache) { - var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); - if (working != null) - return working; + BeatmapManagerWorkingBeatmap working; + + if (!bypassCache) + { + working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); + if (working != null) + return working; + } beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; From d64b236f8619451b3827b3dd1ca6a263ee9c7ea9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 21:27:16 +0900 Subject: [PATCH 002/708] Add a container that provides an isolated gameplay context --- .../Spectate/GameplayIsolationContainer.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs new file mode 100644 index 0000000000..3ccb67d342 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs @@ -0,0 +1,43 @@ +// 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.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public class GameplayIsolationContainer : Container + { + [Cached] + private readonly Bindable ruleset = new Bindable(); + + [Cached] + private readonly Bindable beatmap = new Bindable(); + + [Cached] + private readonly Bindable> mods = new Bindable>(); + + public GameplayIsolationContainer(WorkingBeatmap beatmap, RulesetInfo ruleset, IReadOnlyList mods) + { + this.beatmap.Value = beatmap; + this.ruleset.Value = ruleset; + this.mods.Value = mods; + + beatmap.LoadTrack(); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(ruleset.BeginLease(false)); + dependencies.CacheAs(beatmap.BeginLease(false)); + dependencies.CacheAs(mods.BeginLease(false)); + return dependencies; + } + } +} From 709016f0d639f6899de78d5b2e153ec3775d829a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 22:07:00 +0900 Subject: [PATCH 003/708] Add initial multiplayer screen implementation --- .../Spectate/MultiplayerSpectator.cs | 146 ++++++++++++++++++ .../Spectate/MultiplayerSpectatorPlayer.cs | 19 +++ .../MultiplayerSpectatorPlayerLoader.cs | 26 ++++ .../Multiplayer/Spectate/PlayerGrid.cs | 13 ++ .../Multiplayer/Spectate/PlayerInstance.cs | 55 +++++++ .../Screens/Play/SpectatorPlayerLoader.cs | 7 +- 6 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayerLoader.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs new file mode 100644 index 0000000000..6b8249eae0 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -0,0 +1,146 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Online.Rooms; +using osu.Game.Online.Spectator; +using osu.Game.Screens.Play; +using osu.Game.Screens.Spectate; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public class MultiplayerSpectator : SpectatorScreen + { + private const double min_duration_to_allow_playback = 50; + private const double max_sync_offset = 2; + + // Isolates beatmap/ruleset to this screen. + public override bool DisallowExternalBeatmapRulesetChanges => true; + + public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); + + [Resolved] + private SpectatorStreamingClient spectatorClient { get; set; } + + private readonly int[] userIds; + private readonly PlayerInstance[] instances; + + private PlayerGrid grid; + + public MultiplayerSpectator(PlaylistItem playlistItem, int[] userIds) + : this(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) + { + } + + private MultiplayerSpectator(int[] userIds) + : base(userIds) + { + this.userIds = userIds; + instances = new PlayerInstance[userIds.Length]; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = grid = new PlayerGrid + { + RelativeSizeAxes = Axes.Both + }; + } + + protected override void Update() + { + base.Update(); + updatePlayTime(); + } + + private bool gameplayStarted; + + private void updatePlayTime() + { + if (gameplayStarted) + { + ensurePlaying(instances.Select(i => i.Beatmap.Track.CurrentTime).Max()); + return; + } + + // Make sure all players are loaded. + if (!AllPlayersLoaded) + { + ensureAllStopped(); + return; + } + + if (!instances.All(i => i.Score.Replay.Frames.Count > 0)) + { + ensureAllStopped(); + return; + } + + gameplayStarted = true; + } + + private void ensureAllStopped() + { + foreach (var inst in instances) + inst.ChildrenOfType().SingleOrDefault()?.Stop(); + } + + private readonly BindableDouble catchupFrequencyAdjustment = new BindableDouble(2.0); + + private void ensurePlaying(double targetTime) + { + foreach (var inst in instances) + { + double lastFrameTime = inst.Score.Replay.Frames.Select(f => f.Time).Last(); + double currentTime = inst.Beatmap.Track.CurrentTime; + + // If we have enough frames to play back, start playback. + if (Precision.DefinitelyBigger(lastFrameTime, currentTime, min_duration_to_allow_playback)) + { + inst.ChildrenOfType().Single().Start(); + + if (targetTime < lastFrameTime && targetTime > currentTime + 16) + inst.Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + else + inst.Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + } + else + inst.Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + } + } + + protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) + { + } + + protected override void StartGameplay(int userId, GameplayState gameplayState) + { + int userIndex = getIndexForUser(userId); + var existingInstance = instances[userIndex]; + + if (existingInstance != null) + grid.Remove(existingInstance); + + LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score), d => + { + if (instances[userIndex] == d) + grid.Add(d); + }); + } + + protected override void EndGameplay(int userId) + { + spectatorClient.StopWatchingUser(userId); + } + + private int getIndexForUser(int userId) => Array.IndexOf(userIds, userId); + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs new file mode 100644 index 0000000000..bad093f666 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public class MultiplayerSpectatorPlayer : SpectatorPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public MultiplayerSpectatorPlayer(Score score) + : base(score) + { + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayerLoader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayerLoader.cs new file mode 100644 index 0000000000..c8f16b5e90 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayerLoader.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Scoring; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public class MultiplayerSpectatorPlayerLoader : SpectatorPlayerLoader + { + public MultiplayerSpectatorPlayerLoader(Score score, Func createPlayer) + : base(score, createPlayer) + { + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + } + + protected override void LogoExiting(OsuLogo logo) + { + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs index 830378f129..b2d114d9cc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -75,6 +75,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate cellContainer.Add(cell); } + public void Remove(Drawable content) + { + var cell = cellContainer.FirstOrDefault(c => c.Content == content); + if (cell == null) + return; + + if (cell.IsMaximised) + toggleMaximisationState(cell); + + cellContainer.Remove(cell); + facadeContainer.Remove(facadeContainer[cell.FacadeIndex]); + } + /// /// The content added to this grid. /// diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs new file mode 100644 index 0000000000..ab7d3e2502 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Users; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public class PlayerInstance : CompositeDrawable + { + public bool PlayerLoaded => stack?.CurrentScreen is Player; + + public User User => Score.ScoreInfo.User; + public ScoreProcessor ScoreProcessor => player?.ScoreProcessor; + + public WorkingBeatmap Beatmap { get; private set; } + public Ruleset Ruleset { get; private set; } + + public readonly Score Score; + + private OsuScreenStack stack; + private MultiplayerSpectatorPlayer player; + + public PlayerInstance(Score score) + { + Score = score; + } + + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmapManager) + { + Beatmap = beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap, bypassCache: true); + Ruleset = Score.ScoreInfo.Ruleset.CreateInstance(); + + InternalChild = new GameplayIsolationContainer(Beatmap, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) + { + RelativeSizeAxes = Axes.Both, + Child = new DrawSizePreservingFillContainer + { + RelativeSizeAxes = Axes.Both, + Child = stack = new OsuScreenStack() + } + }; + + stack.Push(new MultiplayerSpectatorPlayerLoader(Score, () => player = new MultiplayerSpectatorPlayer(Score))); + } + } +} diff --git a/osu.Game/Screens/Play/SpectatorPlayerLoader.cs b/osu.Game/Screens/Play/SpectatorPlayerLoader.cs index 580af81166..bdd23962dc 100644 --- a/osu.Game/Screens/Play/SpectatorPlayerLoader.cs +++ b/osu.Game/Screens/Play/SpectatorPlayerLoader.cs @@ -12,7 +12,12 @@ namespace osu.Game.Screens.Play public readonly ScoreInfo Score; public SpectatorPlayerLoader(Score score) - : base(() => new SpectatorPlayer(score)) + : this(score, () => new SpectatorPlayer(score)) + { + } + + public SpectatorPlayerLoader(Score score, Func createPlayer) + : base(createPlayer) { if (score.Replay == null) throw new ArgumentException($"{nameof(score)} must have a non-null {nameof(score.Replay)}.", nameof(score)); From 7d276144b871f219d07321480aea1852e9aa7d44 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 22:13:54 +0900 Subject: [PATCH 004/708] Fix player sizing + masking --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index ab7d3e2502..5689a3222a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -31,6 +31,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public PlayerInstance(Score score) { Score = score; + + RelativeSizeAxes = Axes.Both; + Masking = true; } [BackgroundDependencyLoader] From 1b5679b0d75509eae6ba24761965ec5904020fbd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 22:14:26 +0900 Subject: [PATCH 005/708] Refactor ctor --- .../Multiplayer/Spectate/MultiplayerSpectator.cs | 15 +++------------ osu.Game/Screens/Spectate/SpectatorScreen.cs | 6 +++--- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 6b8249eae0..8fa9dcf3af 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; -using osu.Game.Online.Rooms; using osu.Game.Online.Spectator; using osu.Game.Screens.Play; using osu.Game.Screens.Spectate; @@ -29,20 +28,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private SpectatorStreamingClient spectatorClient { get; set; } - private readonly int[] userIds; private readonly PlayerInstance[] instances; - private PlayerGrid grid; - public MultiplayerSpectator(PlaylistItem playlistItem, int[] userIds) - : this(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) + public MultiplayerSpectator(int[] userIds) + : base(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) { - } - - private MultiplayerSpectator(int[] userIds) - : base(userIds) - { - this.userIds = userIds; instances = new PlayerInstance[userIds.Length]; } @@ -141,6 +132,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate spectatorClient.StopWatchingUser(userId); } - private int getIndexForUser(int userId) => Array.IndexOf(userIds, userId); + private int getIndexForUser(int userId) => Array.IndexOf(UserIds, userId); } } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 6dd3144fc8..b7e1b8496c 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Spectate /// public abstract class SpectatorScreen : OsuScreen { - private readonly int[] userIds; + protected readonly int[] UserIds; [Resolved] private BeatmapManager beatmaps { get; set; } @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Spectate /// The users to spectate. protected SpectatorScreen(params int[] userIds) { - this.userIds = userIds; + this.UserIds = userIds; } protected override void LoadComplete() @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Spectate spectatorClient.OnUserFinishedPlaying += userFinishedPlaying; spectatorClient.OnNewFrames += userSentFrames; - foreach (var id in userIds) + foreach (var id in UserIds) { userLookupCache.GetUserAsync(id).ContinueWith(u => Schedule(() => { From 7958a257e88d04a97e9b72e4a4493eb67dc28141 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 22:14:30 +0900 Subject: [PATCH 006/708] Add tests --- .../Multiplayer/TestSceneMultiplayerScreen.cs | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerScreen.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerScreen.cs new file mode 100644 index 0000000000..d085827986 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerScreen.cs @@ -0,0 +1,327 @@ +// 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 System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Online; +using osu.Game.Online.Spectator; +using osu.Game.Replays.Legacy; +using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerSpectator : MultiplayerTestScene + { + [Cached(typeof(SpectatorStreamingClient))] + private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient(); + + [Cached(typeof(UserLookupCache))] + private UserLookupCache lookupCache = new TestUserLookupCache(); + + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + + private MultiplayerSpectator spectator; + + private readonly List playingUserIds = new List(); + private readonly Dictionary nextFrame = new Dictionary(); + + private BeatmapSetInfo importedSet; + private BeatmapInfo importedBeatmap; + private int importedBeatmapId; + + [BackgroundDependencyLoader] + private void load() + { + importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; + importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1; + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("reset sent frames", () => nextFrame.Clear()); + + AddStep("add streaming client", () => + { + Remove(testSpectatorStreamingClient); + Add(testSpectatorStreamingClient); + }); + + AddStep("finish previous gameplay", () => + { + foreach (var id in playingUserIds) + testSpectatorStreamingClient.EndPlay(id, importedBeatmapId); + playingUserIds.Clear(); + }); + } + + [Test] + public void TestGeneral() + { + int[] userIds = Enumerable.Range(0, 4).Select(i => 55 + i).ToArray(); + + start(userIds); + loadSpectateScreen(); + + sendFrames(userIds, 1000); + AddWaitStep("wait a bit", 20); + } + + [Test] + public void TestPlayersMustStartSimultaneously() + { + start(new[] { 55, 56 }); + loadSpectateScreen(); + + // Send frames for one player only, both should remain paused. + sendFrames(55, 20); + checkPausedInstant(55, true); + checkPausedInstant(56, true); + + // Send frames for the other player, both should now start playing. + sendFrames(56, 20); + checkPausedInstant(55, false); + checkPausedInstant(56, false); + } + + [Test] + public void TestPlayersDoNotStartSimultaneouslyIfBufferingFor15Seconds() + { + start(new[] { 55, 56 }); + loadSpectateScreen(); + + // Send frames for one player only, both should remain paused. + sendFrames(55, 20); + checkPausedInstant(55, true); + checkPausedInstant(56, true); + + // Wait 15 seconds... + AddWaitStep("wait 15 seconds", (int)(15000 / TimePerAction)); + + // Player 1 should start playing by itself, player 2 should remain paused. + checkPausedInstant(55, false); + checkPausedInstant(56, true); + } + + [Test] + public void TestPlayersContinueWhileOthersBuffer() + { + start(new[] { 55, 56 }); + loadSpectateScreen(); + + // Send initial frames for both players. A few more for player 1. + sendFrames(55, 20); + sendFrames(56, 10); + checkPausedInstant(55, false); + checkPausedInstant(56, false); + + // Eventually player 2 will pause, player 1 must remain running. + checkPaused(56, true); + checkPausedInstant(55, false); + + // Eventually both players will run out of frames and should pause. + checkPaused(55, true); + checkPausedInstant(56, true); + + // Send more frames for the first player only. Player 1 should start playing with player 2 remaining paused. + sendFrames(55, 20); + checkPausedInstant(56, true); + checkPausedInstant(55, false); + + // Send more frames for the second player. Both should be playing + sendFrames(56, 20); + checkPausedInstant(56, false); + checkPausedInstant(55, false); + } + + [Test] + public void TestPlayersCatchUpAfterFallingBehind() + { + start(new[] { 55, 56 }); + loadSpectateScreen(); + + // Send initial frames for both players. A few more for player 1. + sendFrames(55, 100); + sendFrames(56, 10); + checkPausedInstant(55, false); + checkPausedInstant(56, false); + + // Eventually player 2 will run out of frames and should pause. + checkPaused(56, true); + AddWaitStep("wait a few more frames", 10); + + // Send more frames for player 2. It should unpause. + sendFrames(56, 100); + checkPausedInstant(56, false); + + // Player 2 should catch up to player 1 after unpausing. + AddUntilStep("player 1 time == player 2 time", () => Precision.AlmostEquals(getGameplayTime(55), getGameplayTime(56), 16)); + } + + private void loadSpectateScreen() + { + AddStep("load screen", () => + { + Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap); + Ruleset.Value = importedBeatmap.Ruleset; + + LoadScreen(spectator = new MultiplayerSpectator(playingUserIds.ToArray())); + }); + + AddUntilStep("wait for screen load", () => spectator.LoadState == LoadState.Loaded && spectator.AllPlayersLoaded); + } + + private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); + + private void start(int[] userIds, int? beatmapId = null) + { + AddStep("start play", () => + { + foreach (int id in userIds) + { + Client.CurrentMatchPlayingUserIds.Add(id); + testSpectatorStreamingClient.StartPlay(id, beatmapId ?? importedBeatmapId); + playingUserIds.Add(id); + nextFrame[id] = 0; + } + }); + } + + private void finish(int userId, int? beatmapId = null) + { + AddStep("end play", () => + { + testSpectatorStreamingClient.EndPlay(userId, beatmapId ?? importedBeatmapId); + playingUserIds.Remove(userId); + nextFrame.Remove(userId); + }); + } + + private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count); + + private void sendFrames(int[] userIds, int count = 10) + { + AddStep("send frames", () => + { + foreach (int id in userIds) + { + testSpectatorStreamingClient.SendFrames(id, nextFrame[id], count); + nextFrame[id] += count; + } + }); + } + + private void checkPaused(int userId, bool state) => + AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().IsPaused.Value == state); + + private void checkPausedInstant(int userId, bool state) => + AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().IsPaused.Value == state); + + private double getGameplayTime(int userId) => getPlayer(userId).ChildrenOfType().Single().GameplayClock.CurrentTime; + + private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); + + private PlayerInstance getInstance(int userId) => spectator.ChildrenOfType().Single(p => p.User.Id == userId); + + public class TestSpectatorStreamingClient : SpectatorStreamingClient + { + private readonly Dictionary userBeatmapDictionary = new Dictionary(); + private readonly Dictionary userSentStateDictionary = new Dictionary(); + + public TestSpectatorStreamingClient() + : base(new DevelopmentEndpointConfiguration()) + { + } + + public void StartPlay(int userId, int beatmapId) + { + userBeatmapDictionary[userId] = beatmapId; + userSentStateDictionary[userId] = false; + + sendState(userId, beatmapId); + } + + public void EndPlay(int userId, int beatmapId) + { + ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState + { + BeatmapID = beatmapId, + RulesetID = 0, + }); + + userSentStateDictionary[userId] = false; + } + + public void SendFrames(int userId, int index, int count) + { + var frames = new List(); + + for (int i = index; i < index + count; i++) + { + var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1; + + frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); + } + + var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames); + ((ISpectatorClient)this).UserSentFrames(userId, bundle); + + if (!userSentStateDictionary[userId]) + sendState(userId, userBeatmapDictionary[userId]); + } + + public override void WatchUser(int userId) + { + if (userSentStateDictionary[userId]) + { + // usually the server would do this. + sendState(userId, userBeatmapDictionary[userId]); + } + + base.WatchUser(userId); + } + + private void sendState(int userId, int beatmapId) + { + ((ISpectatorClient)this).UserBeganPlaying(userId, new SpectatorState + { + BeatmapID = beatmapId, + RulesetID = 0, + }); + + userSentStateDictionary[userId] = true; + } + } + + internal class TestUserLookupCache : UserLookupCache + { + protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) + { + return Task.FromResult(new User + { + Id = lookup, + Username = $"User {lookup}" + }); + } + } + } +} From ecd0b84d944ed82e9de9e244c32230918846055b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 22:15:07 +0900 Subject: [PATCH 007/708] Use max_sync_offset constant --- .../OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 8fa9dcf3af..90cf031584 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { inst.ChildrenOfType().Single().Start(); - if (targetTime < lastFrameTime && targetTime > currentTime + 16) + if (targetTime < lastFrameTime && targetTime > currentTime + max_sync_offset) inst.Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); else inst.Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); From 8005f146a914b26bbe87d763d8ee7d073101711c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 18:37:11 +0900 Subject: [PATCH 008/708] Fix inspection --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index b7e1b8496c..6fa045c962 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Spectate /// The users to spectate. protected SpectatorScreen(params int[] userIds) { - this.UserIds = userIds; + UserIds = userIds; } protected override void LoadComplete() From 4fa51d5ec87a9b5592968737c7e908cfaa0da10b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 18:41:48 +0900 Subject: [PATCH 009/708] Add leaderboard to multiplayer spectate screen --- .../Spectate/MultiplayerSpectator.cs | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 90cf031584..d415669cb1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.Spectator; @@ -30,6 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly PlayerInstance[] instances; private PlayerGrid grid; + private MultiplayerSpectatorLeaderboard leaderboard; public MultiplayerSpectator(int[] userIds) : base(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) @@ -40,10 +42,35 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [BackgroundDependencyLoader] private void load() { - InternalChild = grid = new PlayerGrid + Container leaderboardContainer; + + InternalChild = new GridContainer { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + leaderboardContainer = new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X + }, + grid = new PlayerGrid { RelativeSizeAxes = Axes.Both } + } + } }; + + // Todo: This is not quite correct - it should be per-user to adjust for other mod combinations. + var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); + var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor(); + scoreProcessor.ApplyBeatmap(playableBeatmap); + + LoadComponentAsync(leaderboard = new MultiplayerSpectatorLeaderboard(scoreProcessor, UserIds) { Expanded = { Value = true } }, leaderboardContainer.Add); } protected override void Update() @@ -118,18 +145,25 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate var existingInstance = instances[userIndex]; if (existingInstance != null) + { grid.Remove(existingInstance); + leaderboard.RemoveClock(existingInstance.User.Id); + } LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score), d => { if (instances[userIndex] == d) + { grid.Add(d); + leaderboard.AddClock(d.User.Id, d.Beatmap.Track); + } }); } protected override void EndGameplay(int userId) { spectatorClient.StopWatchingUser(userId); + leaderboard.RemoveClock(userId); } private int getIndexForUser(int userId) => Array.IndexOf(UserIds, userId); From c93ce73123c142499f554fa77b752ec668573876 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 19:58:24 +0900 Subject: [PATCH 010/708] Move catchup logic inside PlayerInstance, fixup some edge cases --- .../Spectate/MultiplayerSpectator.cs | 37 +++------ .../Spectate/MultiplayerSpectatorPlayer.cs | 2 + .../Multiplayer/Spectate/PlayerInstance.cs | 79 ++++++++++++++++++- 3 files changed, 93 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index d415669cb1..19c0d4e742 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -2,16 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.Spectator; -using osu.Game.Screens.Play; using osu.Game.Screens.Spectate; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -19,7 +16,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public class MultiplayerSpectator : SpectatorScreen { private const double min_duration_to_allow_playback = 50; - private const double max_sync_offset = 2; // Isolates beatmap/ruleset to this screen. public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -73,9 +69,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate LoadComponentAsync(leaderboard = new MultiplayerSpectatorLeaderboard(scoreProcessor, UserIds) { Expanded = { Value = true } }, leaderboardContainer.Add); } - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); updatePlayTime(); } @@ -85,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { if (gameplayStarted) { - ensurePlaying(instances.Select(i => i.Beatmap.Track.CurrentTime).Max()); + ensurePlaying(instances.Select(i => i.GetCurrentTrackTime()).Max()); return; } @@ -108,30 +104,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void ensureAllStopped() { foreach (var inst in instances) - inst.ChildrenOfType().SingleOrDefault()?.Stop(); + inst?.PauseGameplay(); } - private readonly BindableDouble catchupFrequencyAdjustment = new BindableDouble(2.0); - - private void ensurePlaying(double targetTime) + private void ensurePlaying(double targetTrackTime) { foreach (var inst in instances) { + Debug.Assert(inst != null); + double lastFrameTime = inst.Score.Replay.Frames.Select(f => f.Time).Last(); - double currentTime = inst.Beatmap.Track.CurrentTime; + double currentTime = inst.GetCurrentGameplayTime(); - // If we have enough frames to play back, start playback. - if (Precision.DefinitelyBigger(lastFrameTime, currentTime, min_duration_to_allow_playback)) - { - inst.ChildrenOfType().Single().Start(); + bool canContinuePlayback = Precision.DefinitelyBigger(lastFrameTime, currentTime, min_duration_to_allow_playback); + if (!canContinuePlayback) + continue; - if (targetTime < lastFrameTime && targetTime > currentTime + max_sync_offset) - inst.Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); - else - inst.Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); - } - else - inst.Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + inst.ContinueGameplay(targetTrackTime); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index bad093f666..1634438850 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -11,6 +11,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + public MultiplayerSpectatorPlayer(Score score) : base(score) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 5689a3222a..acac753fe2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -15,10 +17,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class PlayerInstance : CompositeDrawable { + private const double catchup_rate = 2; + private const double max_sync_offset = catchup_rate * 2; // Double the catchup rate to prevent ringing. + public bool PlayerLoaded => stack?.CurrentScreen is Player; public User User => Score.ScoreInfo.User; - public ScoreProcessor ScoreProcessor => player?.ScoreProcessor; public WorkingBeatmap Beatmap { get; private set; } public Ruleset Ruleset { get; private set; } @@ -54,5 +58,78 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate stack.Push(new MultiplayerSpectatorPlayerLoader(Score, () => player = new MultiplayerSpectatorPlayer(Score))); } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + updateCatchup(); + } + + private readonly BindableDouble catchupFrequencyAdjustment = new BindableDouble(catchup_rate); + private double targetTrackTime; + private bool isCatchingUp; + + private void updateCatchup() + { + if (player?.IsLoaded != true) + return; + + if (Score.Replay.Frames.Count == 0) + return; + + if (player.GameplayClockContainer.IsPaused.Value) + return; + + double currentTime = Beatmap.Track.CurrentTime; + bool catchupRequired = targetTrackTime > currentTime + max_sync_offset; + + // Skip catchup if nothing needs to be done. + if (catchupRequired == isCatchingUp) + return; + + if (catchupRequired) + { + Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + isCatchingUp = true; + } + else + { + Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + isCatchingUp = false; + } + } + + public double GetCurrentGameplayTime() + { + if (player?.IsLoaded != true) + return 0; + + return player.GameplayClockContainer.GameplayClock.CurrentTime; + } + + public double GetCurrentTrackTime() + { + if (player?.IsLoaded != true) + return 0; + + return Beatmap.Track.CurrentTime; + } + + public void ContinueGameplay(double targetTrackTime) + { + if (player?.IsLoaded != true) + return; + + player.GameplayClockContainer.Start(); + this.targetTrackTime = targetTrackTime; + } + + public void PauseGameplay() + { + if (player?.IsLoaded != true) + return; + + player.GameplayClockContainer.Stop(); + } } } From 49b7519c53a765d9bb4576dd1f0c529a0027fd24 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 20:03:34 +0900 Subject: [PATCH 011/708] Refactor gameplay starting logic --- .../Spectate/MultiplayerSpectator.cs | 36 ++++--------------- .../Multiplayer/Spectate/PlayerInstance.cs | 1 - 2 files changed, 7 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 19c0d4e742..b0747f5027 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -72,43 +72,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - updatePlayTime(); + updateGameplayPlayingState(); } - private bool gameplayStarted; - - private void updatePlayTime() + private void updateGameplayPlayingState() { - if (gameplayStarted) + // Make sure all players are loaded and have frames before starting any. + if (!AllPlayersLoaded || !instances.All(i => i.Score.Replay.Frames.Count > 0)) { - ensurePlaying(instances.Select(i => i.GetCurrentTrackTime()).Max()); + foreach (var inst in instances) + inst?.PauseGameplay(); return; } - // Make sure all players are loaded. - if (!AllPlayersLoaded) - { - ensureAllStopped(); - return; - } + double targetTrackTime = instances.Select(i => i.GetCurrentGameplayTime()).Max(); - if (!instances.All(i => i.Score.Replay.Frames.Count > 0)) - { - ensureAllStopped(); - return; - } - - gameplayStarted = true; - } - - private void ensureAllStopped() - { - foreach (var inst in instances) - inst?.PauseGameplay(); - } - - private void ensurePlaying(double targetTrackTime) - { foreach (var inst in instances) { Debug.Assert(inst != null); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index acac753fe2..ca58d888fa 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Users; From eccd269ccee483973c260daa44a0dddc7740f9c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 20:17:57 +0900 Subject: [PATCH 012/708] Implement maximum start delay --- .../Multiplayer/TestSceneMultiplayerScreen.cs | 2 +- .../Spectate/MultiplayerSpectator.cs | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerScreen.cs index d085827986..897219dbad 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerScreen.cs @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Multiplayer loadSpectateScreen(); // Send frames for one player only, both should remain paused. - sendFrames(55, 20); + sendFrames(55, 1000); checkPausedInstant(55, true); checkPausedInstant(56, true); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index b0747f5027..d8ec25fedb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.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.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -16,6 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public class MultiplayerSpectator : SpectatorScreen { private const double min_duration_to_allow_playback = 50; + private const double maximum_start_delay = 15000; // Isolates beatmap/ruleset to this screen. public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -28,6 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly PlayerInstance[] instances; private PlayerGrid grid; private MultiplayerSpectatorLeaderboard leaderboard; + private double? loadStartTime; public MultiplayerSpectator(int[] userIds) : base(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) @@ -72,22 +75,39 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); + + loadStartTime ??= Time.Current; + updateGameplayPlayingState(); } + private bool canStartGameplay => + // All players must be loaded. + AllPlayersLoaded + && ( + // All players have frames... + instances.All(i => i.Score.Replay.Frames.Count > 0) + // Or any player has frames and the maximum start delay has been exceeded. + || (Time.Current - loadStartTime > maximum_start_delay + && instances.Any(i => i.Score.Replay.Frames.Count > 0)) + ); + private void updateGameplayPlayingState() { // Make sure all players are loaded and have frames before starting any. - if (!AllPlayersLoaded || !instances.All(i => i.Score.Replay.Frames.Count > 0)) + if (!canStartGameplay) { foreach (var inst in instances) inst?.PauseGameplay(); return; } - double targetTrackTime = instances.Select(i => i.GetCurrentGameplayTime()).Max(); + // Not all instances may be in a valid gameplay state (see canStartGameplay). Only control the ones that are. + IEnumerable validInstances = instances.Where(i => i.Score.Replay.Frames.Count > 0); - foreach (var inst in instances) + double targetTrackTime = validInstances.Select(i => i.GetCurrentTrackTime()).Max(); + + foreach (var inst in validInstances) { Debug.Assert(inst != null); From 61c400b1a1187a17a17467a02a061fb87dca65f4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 20:18:26 +0900 Subject: [PATCH 013/708] Fix filename --- ...SceneMultiplayerScreen.cs => TestSceneMultiplayerSpectator.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/Multiplayer/{TestSceneMultiplayerScreen.cs => TestSceneMultiplayerSpectator.cs} (100%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs similarity index 100% rename from osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerScreen.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs From 3e46d6401eeaef7a63ed6506f8b8d2840b53767c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 20:22:30 +0900 Subject: [PATCH 014/708] Remove some unnecessary code --- .../Multiplayer/Spectate/MultiplayerSpectator.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index d8ec25fedb..db5de45f72 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -82,7 +82,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } private bool canStartGameplay => - // All players must be loaded. + // All players must be loaded, and... AllPlayersLoaded && ( // All players have frames... diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index ca58d888fa..6b5f102543 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; -using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Users; @@ -24,7 +23,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public User User => Score.ScoreInfo.User; public WorkingBeatmap Beatmap { get; private set; } - public Ruleset Ruleset { get; private set; } public readonly Score Score; @@ -43,7 +41,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void load(BeatmapManager beatmapManager) { Beatmap = beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap, bypassCache: true); - Ruleset = Score.ScoreInfo.Ruleset.CreateInstance(); InternalChild = new GameplayIsolationContainer(Beatmap, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { @@ -87,15 +84,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return; if (catchupRequired) - { Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); - isCatchingUp = true; - } else - { Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); - isCatchingUp = false; - } + + isCatchingUp = catchupRequired; } public double GetCurrentGameplayTime() From 6eddc6c59e6c63c219798bc0fb92a59e0d3c3b96 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 21:03:45 +0900 Subject: [PATCH 015/708] Enable spectating multiplayer matches --- .../Online/Multiplayer/MultiplayerClient.cs | 3 --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 6 ++++-- .../Multiplayer/MultiplayerMatchSubScreen.cs | 20 +++++++++++++++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 37e11cc576..4529dfd0a7 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -96,9 +96,6 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; - if (newState == MultiplayerUserState.Spectating) - return Task.CompletedTask; // Not supported yet. - return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState); } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 706da05d15..0d1ed5d88e 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -148,10 +148,12 @@ namespace osu.Game.Screens.OnlinePlay.Match return base.OnExiting(next); } - protected void StartPlay(Func player) + protected void StartPlay(Func player) => PushTopLevelScreen(() => new PlayerLoader(player)); + + protected void PushTopLevelScreen(Func screen) { sampleStart?.Play(); - ParentScreen?.Push(new PlayerLoader(player)); + ParentScreen?.Push(screen()); } private void selectedItemChanged() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 90cef0107c..035eba4f30 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -25,6 +25,7 @@ using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osuTK; @@ -405,11 +406,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } - private void onRoomUpdated() + private void onRoomUpdated() => Scheduler.Add(() => { - // user mods may have changed. - Scheduler.AddOnce(UpdateMods); - } + if (client.Room == null) + return; + + Debug.Assert(client.LocalUser != null); + + UpdateMods(); + + if (client.LocalUser.State == MultiplayerUserState.Spectating + && (client.Room.State == MultiplayerRoomState.Playing || client.Room.State == MultiplayerRoomState.WaitingForLoad) + && ParentScreen.IsCurrentScreen()) + { + PushTopLevelScreen(() => new MultiplayerSpectator(client.CurrentMatchPlayingUserIds.ToArray())); + } + }); private void onLoadRequested() { From 4409c1a36f18dbae1484dd6ef65413a275e0b57b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 22:01:21 +0900 Subject: [PATCH 016/708] Increase sync offset to prevent constant catchups --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 6b5f102543..f0994ec375 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public class PlayerInstance : CompositeDrawable { private const double catchup_rate = 2; - private const double max_sync_offset = catchup_rate * 2; // Double the catchup rate to prevent ringing. + private const double max_sync_offset = 50; public bool PlayerLoaded => stack?.CurrentScreen is Player; From 16d34bcc0a04b319a53d86f20a863a837eb8d73d Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 12 Apr 2021 16:02:19 -0400 Subject: [PATCH 017/708] 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 627dd960b07198135b941c1a82a70338058fd21d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Apr 2021 20:52:20 +0900 Subject: [PATCH 018/708] Disable player input for now --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index f0994ec375..32cc3b48d6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -123,5 +123,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate player.GameplayClockContainer.Stop(); } + + // Player interferes with global input, so disable input for now. + public override bool PropagatePositionalInputSubTree => false; + public override bool PropagateNonPositionalInputSubTree => false; } } From 20823abb309ac0fd908bdd03b532abfbdbd22afd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Apr 2021 22:10:35 +0900 Subject: [PATCH 019/708] Make resyncing a bit more resilient --- .../TestSceneMultiplayerSpectator.cs | 33 ++++++++++++++++- .../Multiplayer/Spectate/PlayerInstance.cs | 35 +++++++++++++++---- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs index 897219dbad..1090f1d10e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; @@ -153,6 +156,27 @@ namespace osu.Game.Tests.Visual.Multiplayer checkPausedInstant(55, false); } + [Test] + public void TestPlayerStartsCatchingUpOnlyAfterExceedingMaxOffset() + { + start(new[] { 55, 56 }); + loadSpectateScreen(); + + sendFrames(55, 1000); + sendFrames(56, 1000); + + Bindable slowDownAdjustment; + + AddStep("slow down player 2", () => + { + slowDownAdjustment = new Bindable(0.99); + getInstance(56).Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, slowDownAdjustment); + }); + + AddUntilStep("exceeded min offset but not catching up", () => getGameplayOffset(55, 56) > PlayerInstance.MAX_OFFSET && !getInstance(56).IsCatchingUp); + AddUntilStep("catching up or caught up", () => getInstance(56).IsCatchingUp || Math.Abs(getGameplayOffset(55, 56)) < PlayerInstance.SYNC_TARGET * 2); + } + [Test] public void TestPlayersCatchUpAfterFallingBehind() { @@ -174,7 +198,9 @@ namespace osu.Game.Tests.Visual.Multiplayer checkPausedInstant(56, false); // Player 2 should catch up to player 1 after unpausing. - AddUntilStep("player 1 time == player 2 time", () => Precision.AlmostEquals(getGameplayTime(55), getGameplayTime(56), 16)); + AddUntilStep("player 2 not catching up", () => !getInstance(56).IsCatchingUp); + AddAssert("player 1 time == player 2 time", () => Math.Abs(getGameplayOffset(55, 56)) <= 2 * PlayerInstance.SYNC_TARGET); + AddWaitStep("wait a bit", 5); } private void loadSpectateScreen() @@ -236,6 +262,11 @@ namespace osu.Game.Tests.Visual.Multiplayer private void checkPausedInstant(int userId, bool state) => AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().IsPaused.Value == state); + /// + /// Returns time(user1) - time(user2). + /// + private double getGameplayOffset(int user1, int user2) => getGameplayTime(user1) - getGameplayTime(user2); + private double getGameplayTime(int userId) => getPlayer(userId).ChildrenOfType().Single().GameplayClock.CurrentTime; private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 32cc3b48d6..a4a3a0c133 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -6,6 +6,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -15,8 +16,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class PlayerInstance : CompositeDrawable { + /// + /// The rate at which a user catches up after becoming desynchronised. + /// private const double catchup_rate = 2; - private const double max_sync_offset = 50; + + /// + /// The offset from the expected time at which to START synchronisation. + /// + public const double MAX_OFFSET = 50; + + /// + /// The maximum offset from the expected time at which to STOP synchronisation. + /// + public const double SYNC_TARGET = 16; public bool PlayerLoaded => stack?.CurrentScreen is Player; @@ -26,6 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public readonly Score Score; + public bool IsCatchingUp { get; private set; } + private OsuScreenStack stack; private MultiplayerSpectatorPlayer player; @@ -63,7 +78,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly BindableDouble catchupFrequencyAdjustment = new BindableDouble(catchup_rate); private double targetTrackTime; - private bool isCatchingUp; private void updateCatchup() { @@ -77,18 +91,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return; double currentTime = Beatmap.Track.CurrentTime; - bool catchupRequired = targetTrackTime > currentTime + max_sync_offset; + double timeBehind = targetTrackTime - currentTime; - // Skip catchup if nothing needs to be done. - if (catchupRequired == isCatchingUp) + double offsetForCatchup = IsCatchingUp ? SYNC_TARGET : MAX_OFFSET; + bool catchupRequired = timeBehind > offsetForCatchup; + + // Skip catchup if no work needs to be done. + if (catchupRequired == IsCatchingUp) return; if (catchupRequired) + { Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + Logger.Log($"{User.Id} catchup started (behind: {timeBehind})"); + } else + { Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + Logger.Log($"{User.Id} catchup finished (behind: {timeBehind})"); + } - isCatchingUp = catchupRequired; + IsCatchingUp = catchupRequired; } public double GetCurrentGameplayTime() From 3039b7b0f9731e881dc79f23a741d7b3b29df18d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Apr 2021 22:40:10 +0900 Subject: [PATCH 020/708] Make tests a bit more resilient --- .../Visual/Multiplayer/TestSceneMultiplayerSpectator.cs | 3 +-- .../Multiplayer/Spectate/MultiplayerSpectator.cs | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs index 1090f1d10e..1ac012c0b5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs @@ -199,8 +199,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // Player 2 should catch up to player 1 after unpausing. AddUntilStep("player 2 not catching up", () => !getInstance(56).IsCatchingUp); - AddAssert("player 1 time == player 2 time", () => Math.Abs(getGameplayOffset(55, 56)) <= 2 * PlayerInstance.SYNC_TARGET); - AddWaitStep("wait a bit", 5); + AddWaitStep("wait a bit", 10); } private void loadSpectateScreen() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index db5de45f72..636e7b9a49 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly PlayerInstance[] instances; private PlayerGrid grid; private MultiplayerSpectatorLeaderboard leaderboard; - private double? loadStartTime; + private double? loadFinishTime; public MultiplayerSpectator(int[] userIds) : base(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) @@ -76,7 +76,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { base.UpdateAfterChildren(); - loadStartTime ??= Time.Current; + if (AllPlayersLoaded) + loadFinishTime ??= Time.Current; updateGameplayPlayingState(); } @@ -88,7 +89,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate // All players have frames... instances.All(i => i.Score.Replay.Frames.Count > 0) // Or any player has frames and the maximum start delay has been exceeded. - || (Time.Current - loadStartTime > maximum_start_delay + || (Time.Current - loadFinishTime > maximum_start_delay && instances.Any(i => i.Score.Replay.Frames.Count > 0)) ); From d49b90877e906960cfc081894cc3ecd8687f35db Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Apr 2021 23:21:35 +0900 Subject: [PATCH 021/708] Fix operation remaining in progress --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 035eba4f30..9a68ff908d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -420,6 +420,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer && ParentScreen.IsCurrentScreen()) { PushTopLevelScreen(() => new MultiplayerSpectator(client.CurrentMatchPlayingUserIds.ToArray())); + + // If the current user was host, they started the match and the in-progres operation needs to be stopped now. + readyClickOperation?.Dispose(); + readyClickOperation = null; } }); From 56e1bffdfd237ceb45db074982b912e27612640e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Apr 2021 23:40:10 +0900 Subject: [PATCH 022/708] Populate initial user states --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 2ddc10db0f..fe1201ef89 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -144,6 +144,9 @@ 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 77830527e7e06df39fbf8b8ae341b9847bef2795 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Apr 2021 23:49:23 +0900 Subject: [PATCH 023/708] Fix spectate button being disabled during play --- .../OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 4b3fb5d00f..465af037e2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match break; } - button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; + button.Enabled.Value = !operationInProgress.Value; } private class ButtonWithTrianglesExposed : TriangleButton From 69b01e727008bb8dd7d15bce834e72aee9ae3ca1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 00:58:03 +0900 Subject: [PATCH 024/708] Add some debugging --- .../OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs | 4 ++++ .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 636e7b9a49..a56104439d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Utils; using osu.Game.Online.Spectator; using osu.Game.Screens.Spectate; @@ -108,6 +109,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate double targetTrackTime = validInstances.Select(i => i.GetCurrentTrackTime()).Max(); + var instanceTimes = string.Join(',', validInstances.Select(i => $" {i.User.Id}: {(int)i.GetCurrentTrackTime()}")); + Logger.Log($"target: {(int)targetTrackTime},{instanceTimes}"); + foreach (var inst in validInstances) { Debug.Assert(inst != null); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index a4a3a0c133..12a710e9f0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -103,12 +103,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (catchupRequired) { Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); - Logger.Log($"{User.Id} catchup started (behind: {timeBehind})"); + Logger.Log($"{User.Id} catchup started (behind: {(int)timeBehind})"); } else { Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); - Logger.Log($"{User.Id} catchup finished (behind: {timeBehind})"); + Logger.Log($"{User.Id} catchup finished (behind: {(int)timeBehind})"); } IsCatchingUp = catchupRequired; From 774cca38c4c66627c3f501712f9e40cba1ef8474 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 20:39:14 +0900 Subject: [PATCH 025/708] Make spectating instances use custom GCC --- .../Spectate/MultiplayerSpectator.cs | 48 ++++++++++++------- .../Spectate/MultiplayerSpectatorPlayer.cs | 33 ++++++++++++- .../Multiplayer/Spectate/PlayerInstance.cs | 29 ++++++----- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index a56104439d..931d13942b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Utils; using osu.Game.Online.Spectator; +using osu.Game.Screens.Play; using osu.Game.Screens.Spectate; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -29,6 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private SpectatorStreamingClient spectatorClient { get; set; } private readonly PlayerInstance[] instances; + private GameplayClockContainer gameplayClockContainer; private PlayerGrid grid; private MultiplayerSpectatorLeaderboard leaderboard; private double? loadFinishTime; @@ -44,23 +46,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { Container leaderboardContainer; - InternalChild = new GridContainer + InternalChild = gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0) { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + Child = new GridContainer { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - leaderboardContainer = new Container + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X - }, - grid = new PlayerGrid { RelativeSizeAxes = Axes.Both } + leaderboardContainer = new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X + }, + grid = new PlayerGrid { RelativeSizeAxes = Axes.Both } + } } } }; @@ -94,6 +99,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate && instances.Any(i => i.Score.Replay.Frames.Count > 0)) ); + private bool firstStartFrame = true; + private void updateGameplayPlayingState() { // Make sure all players are loaded and have frames before starting any. @@ -104,13 +111,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return; } + if (firstStartFrame) + gameplayClockContainer.Restart(); + // Not all instances may be in a valid gameplay state (see canStartGameplay). Only control the ones that are. IEnumerable validInstances = instances.Where(i => i.Score.Replay.Frames.Count > 0); - double targetTrackTime = validInstances.Select(i => i.GetCurrentTrackTime()).Max(); + double targetGameplayTime = gameplayClockContainer.GameplayClock.CurrentTime; - var instanceTimes = string.Join(',', validInstances.Select(i => $" {i.User.Id}: {(int)i.GetCurrentTrackTime()}")); - Logger.Log($"target: {(int)targetTrackTime},{instanceTimes}"); + var instanceTimes = string.Join(',', validInstances.Select(i => $" {i.User.Id}: {(int)i.GetCurrentGameplayTime()}")); + Logger.Log($"target: {(int)targetGameplayTime},{instanceTimes}"); foreach (var inst in validInstances) { @@ -123,8 +133,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (!canContinuePlayback) continue; - inst.ContinueGameplay(targetTrackTime); + inst.ContinueGameplay(targetGameplayTime); } + + firstStartFrame = false; } protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) @@ -142,7 +154,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate leaderboard.RemoveClock(existingInstance.User.Id); } - LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score), d => + LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score, gameplayClockContainer.GameplayClock), d => { if (instances[userIndex] == d) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index 1634438850..abb7ec83d8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -1,6 +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.Bindables; +using osu.Framework.Timing; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -11,11 +14,37 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + public new SubGameplayClockContainer GameplayClockContainer => (SubGameplayClockContainer)base.GameplayClockContainer; - public MultiplayerSpectatorPlayer(Score score) + private readonly GameplayClock gameplayClock; + + public MultiplayerSpectatorPlayer(Score score, GameplayClock gameplayClock) : base(score) { + this.gameplayClock = gameplayClock; } + + protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) + => new SubGameplayClockContainer(gameplayClock); + } + + public class SubGameplayClockContainer : GameplayClockContainer + { + public new DecoupleableInterpolatingFramedClock AdjustableClock => base.AdjustableClock; + + public SubGameplayClockContainer(IClock sourceClock) + : base(sourceClock) + { + } + + protected override void OnIsPausedChanged(ValueChangedEvent isPaused) + { + if (isPaused.NewValue) + AdjustableClock.Stop(); + else + AdjustableClock.Start(); + } + + protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) => new GameplayClock(source); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 12a710e9f0..724a685cb7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -38,15 +36,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public WorkingBeatmap Beatmap { get; private set; } public readonly Score Score; + private readonly GameplayClock gameplayClock; public bool IsCatchingUp { get; private set; } private OsuScreenStack stack; private MultiplayerSpectatorPlayer player; - public PlayerInstance(Score score) + public PlayerInstance(Score score, GameplayClock gameplayClock) { Score = score; + this.gameplayClock = gameplayClock; RelativeSizeAxes = Axes.Both; Masking = true; @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } }; - stack.Push(new MultiplayerSpectatorPlayerLoader(Score, () => player = new MultiplayerSpectatorPlayer(Score))); + stack.Push(new MultiplayerSpectatorPlayerLoader(Score, () => player = new MultiplayerSpectatorPlayer(Score, gameplayClock))); } protected override void UpdateAfterChildren() @@ -76,8 +76,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate updateCatchup(); } - private readonly BindableDouble catchupFrequencyAdjustment = new BindableDouble(catchup_rate); - private double targetTrackTime; + private double targetGameplayTime; private void updateCatchup() { @@ -91,7 +90,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return; double currentTime = Beatmap.Track.CurrentTime; - double timeBehind = targetTrackTime - currentTime; + double timeBehind = targetGameplayTime - currentTime; double offsetForCatchup = IsCatchingUp ? SYNC_TARGET : MAX_OFFSET; bool catchupRequired = timeBehind > offsetForCatchup; @@ -102,12 +101,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (catchupRequired) { - Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + // player.GameplayClockContainer.AdjustableClock.Rate = catchup_rate; Logger.Log($"{User.Id} catchup started (behind: {(int)timeBehind})"); } else { - Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + // player.GameplayClockContainer.AdjustableClock.Rate = 1; Logger.Log($"{User.Id} catchup finished (behind: {(int)timeBehind})"); } @@ -122,21 +121,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return player.GameplayClockContainer.GameplayClock.CurrentTime; } - public double GetCurrentTrackTime() + public bool IsPlaying() { - if (player?.IsLoaded != true) - return 0; + if (player.IsLoaded != true) + return false; - return Beatmap.Track.CurrentTime; + return player.GameplayClockContainer.GameplayClock.IsRunning; } - public void ContinueGameplay(double targetTrackTime) + public void ContinueGameplay(double targetGameplayTime) { if (player?.IsLoaded != true) return; player.GameplayClockContainer.Start(); - this.targetTrackTime = targetTrackTime; + this.targetGameplayTime = targetGameplayTime; } public void PauseGameplay() From 6fc7488a67ddf580358369327c78ddea5d7aab8d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 16:33:59 +0900 Subject: [PATCH 026/708] Reimplement syncing logic as a new component --- .../OnlinePlay/MultiplayerSyncManagerTest.cs | 213 ++++++++++++++++++ .../Spectate/IMultiplayerSlaveClock.cs | 17 ++ .../Spectate/IMultiplayerSyncManager.cs | 16 ++ .../Spectate/MultiplayerSyncManager.cs | 162 +++++++++++++ 4 files changed, 408 insertions(+) create mode 100644 osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSyncManager.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs diff --git a/osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs b/osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs new file mode 100644 index 0000000000..2a6dfa9c8d --- /dev/null +++ b/osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs @@ -0,0 +1,213 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.OnlinePlay +{ + [HeadlessTest] + public class MultiplayerSyncManagerTest : OsuTestScene + { + private TestManualClock master; + private MultiplayerSyncManager syncManager; + + private TestSlaveClock slave1; + private TestSlaveClock slave2; + + [SetUp] + public void Setup() + { + syncManager = new MultiplayerSyncManager(master = new TestManualClock()); + syncManager.AddSlave(slave1 = new TestSlaveClock(1)); + syncManager.AddSlave(slave2 = new TestSlaveClock(2)); + + Schedule(() => Child = syncManager); + } + + [Test] + public void TestMasterClockStartsWhenAllSlavesHaveFrames() + { + setWaiting(() => slave1, false); + assertMasterState(false); + assertSlaveState(() => slave1, false); + assertSlaveState(() => slave2, false); + + setWaiting(() => slave2, false); + assertMasterState(true); + assertSlaveState(() => slave1, true); + assertSlaveState(() => slave2, true); + } + + [Test] + public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime() + { + AddWaitStep($"wait {MultiplayerSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(MultiplayerSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + assertMasterState(false); + } + + [Test] + public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime() + { + setWaiting(() => slave1, false); + AddWaitStep($"wait {MultiplayerSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(MultiplayerSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + assertMasterState(true); + } + + [Test] + public void TestSlaveDoesNotCatchUpWhenSlightlyOutOfSync() + { + setAllWaiting(false); + + setMasterTime(MultiplayerSyncManager.SYNC_TARGET + 1); + assertCatchingUp(() => slave1, false); + } + + [Test] + public void TestSlaveStartsCatchingUpWhenTooFarBehind() + { + setAllWaiting(false); + + setMasterTime(MultiplayerSyncManager.MAX_SYNC_OFFSET + 1); + assertCatchingUp(() => slave1, true); + assertCatchingUp(() => slave2, true); + } + + [Test] + public void TestSlaveKeepsCatchingUpWhenSlightlyOutOfSync() + { + setAllWaiting(false); + + setMasterTime(MultiplayerSyncManager.MAX_SYNC_OFFSET + 1); + setSlaveTime(() => slave1, MultiplayerSyncManager.SYNC_TARGET + 1); + assertCatchingUp(() => slave1, true); + } + + [Test] + public void TestSlaveStopsCatchingUpWhenInSync() + { + setAllWaiting(false); + + setMasterTime(MultiplayerSyncManager.MAX_SYNC_OFFSET + 2); + setSlaveTime(() => slave1, MultiplayerSyncManager.SYNC_TARGET); + assertCatchingUp(() => slave1, false); + assertCatchingUp(() => slave2, true); + } + + [Test] + public void TestSlaveDoesNotStopWhenSlightlyAhead() + { + setAllWaiting(false); + + setSlaveTime(() => slave1, -MultiplayerSyncManager.SYNC_TARGET); + assertCatchingUp(() => slave1, false); + assertSlaveState(() => slave1, true); + } + + [Test] + public void TestSlaveStopsWhenTooFarAheadAndStartsWhenBackInSync() + { + setAllWaiting(false); + + setSlaveTime(() => slave1, -MultiplayerSyncManager.SYNC_TARGET - 1); + + // This is a silent catchup, where IsCatchingUp = false but IsRunning = false also. + assertCatchingUp(() => slave1, false); + assertSlaveState(() => slave1, false); + + setMasterTime(1); + assertCatchingUp(() => slave1, false); + assertSlaveState(() => slave1, true); + } + + [Test] + public void TestInSyncSlaveDoesNotStartIfWaitingOnFrames() + { + setAllWaiting(false); + + assertSlaveState(() => slave1, true); + setWaiting(() => slave1, true); + assertSlaveState(() => slave1, false); + } + + private void setWaiting(Func slave, bool waiting) + => AddStep($"set slave {slave().Id} waiting = {waiting}", () => slave().WaitingOnFrames.Value = waiting); + + private void setAllWaiting(bool waiting) => AddStep($"set all slaves waiting = {waiting}", () => + { + slave1.WaitingOnFrames.Value = waiting; + slave2.WaitingOnFrames.Value = waiting; + }); + + private void setMasterTime(double time) + => AddStep($"set master = {time}", () => master.Seek(time)); + + /// + /// slave.Time = master.Time - offsetFromMaster + /// + private void setSlaveTime(Func slave, double offsetFromMaster) + => AddStep($"set slave {slave().Id} = master - {offsetFromMaster}", () => slave().Seek(master.CurrentTime - offsetFromMaster)); + + private void assertMasterState(bool running) + => AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running); + + private void assertCatchingUp(Func slave, bool catchingUp) => + AddAssert($"slave {slave().Id} {(catchingUp ? "is" : "is not")} catching up", () => slave().IsCatchingUp == catchingUp); + + private void assertSlaveState(Func slave, bool running) + => AddAssert($"slave {slave().Id} {(running ? "is" : "is not")} running", () => slave().IsRunning == running); + + private class TestSlaveClock : TestManualClock, IMultiplayerSlaveClock + { + public readonly Bindable WaitingOnFrames = new Bindable(true); + IBindable IMultiplayerSlaveClock.WaitingOnFrames => WaitingOnFrames; + + public double LastFrameTime => 0; + + double IMultiplayerSlaveClock.LastFrameTime => LastFrameTime; + + public bool IsCatchingUp { get; set; } + + public readonly int Id; + + public TestSlaveClock(int id) + { + Id = id; + + WaitingOnFrames.BindValueChanged(waiting => + { + if (waiting.NewValue) + Stop(); + else + Start(); + }); + } + } + + private class TestManualClock : ManualClock, IAdjustableClock + { + public void Start() => IsRunning = true; + + public void Stop() => IsRunning = false; + + public bool Seek(double position) + { + CurrentTime = position; + return true; + } + + public void Reset() + { + } + + public void ResetSpeedAdjustments() + { + } + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs new file mode 100644 index 0000000000..e0fca45bdd --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.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.Framework.Bindables; +using osu.Framework.Timing; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public interface IMultiplayerSlaveClock : IAdjustableClock + { + IBindable WaitingOnFrames { get; } + + double LastFrameTime { get; } + + bool IsCatchingUp { get; set; } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSyncManager.cs new file mode 100644 index 0000000000..2c50596823 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSyncManager.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Timing; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public interface IMultiplayerSyncManager + { + IAdjustableClock Master { get; } + + void AddSlave(IMultiplayerSlaveClock clock); + + void RemoveSlave(IMultiplayerSlaveClock clock); + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs new file mode 100644 index 0000000000..42f6536e90 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs @@ -0,0 +1,162 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Framework.Timing; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public class MultiplayerSyncManager : Component, IMultiplayerSyncManager + { + /// + /// The offset from the master clock to which slaves should be synchronised to. + /// + public const double SYNC_TARGET = 16; + + /// + /// The offset from the master clock at which slaves begin resynchronising. + /// + public const double MAX_SYNC_OFFSET = 50; + + /// + /// The maximum delay to start gameplay, if any (but not all) slaves are ready. + /// + public const double MAXIMUM_START_DELAY = 15000; + + /// + /// The catchup rate. + /// + public const double CATCHUP_RATE = 2; + + /// + /// The master clock which is used to control the timing of all slave clocks. + /// + public IAdjustableClock Master { get; } + + /// + /// The slave clocks. + /// + private readonly List slaves = new List(); + + private bool hasStarted; + private double? firstStartAttemptTime; + + public MultiplayerSyncManager(IAdjustableClock master) + { + Master = master; + } + + public void AddSlave(IMultiplayerSlaveClock clock) => slaves.Add(clock); + + public void RemoveSlave(IMultiplayerSlaveClock clock) => slaves.Remove(clock); + + protected override void Update() + { + base.Update(); + + if (!attemptStart()) + { + // Ensure all slaves are stopped until the start succeeds. + foreach (var slave in slaves) + slave.Stop(); + return; + } + + updateCatchup(); + updateMasterClock(); + } + + /// + /// Attempts to start playback. Awaits for all slaves to have available frames for up to milliseconds. + /// + /// Whether playback was started and syncing should occur. + private bool attemptStart() + { + if (hasStarted) + return true; + + if (slaves.Count == 0) + return false; + + firstStartAttemptTime ??= Time.Current; + + int readyCount = slaves.Count(s => !s.WaitingOnFrames.Value); + + if (readyCount == slaves.Count) + { + Logger.Log("Gameplay started (all ready)."); + return hasStarted = true; + } + + if (readyCount > 0 && (Time.Current - firstStartAttemptTime) > MAXIMUM_START_DELAY) + { + Logger.Log($"Gameplay started (maximum delay exceeded, {readyCount}/{slaves.Count} ready)."); + return hasStarted = true; + } + + return false; + } + + /// + /// Updates the catchup states of all slave clocks. + /// + private void updateCatchup() + { + for (int i = 0; i < slaves.Count; i++) + { + var slave = slaves[i]; + double timeDelta = Master.CurrentTime - slave.CurrentTime; + + // Check that the slave isn't too far ahead. + // This is a quiet case in which the catchup is done by the master clock, so IsCatchingUp is not set on the slave. + if (timeDelta < -SYNC_TARGET) + { + slave.Stop(); + continue; + } + + // Make sure the slave is running if it can. + if (!slave.WaitingOnFrames.Value) + slave.Start(); + + if (slave.IsCatchingUp) + { + // Stop the slave from catching up if it's within the sync target. + if (timeDelta <= SYNC_TARGET) + { + slave.IsCatchingUp = false; + Logger.Log($"Slave {i} catchup finished (delta = {timeDelta})"); + } + } + else + { + // Make the slave start catching up if it's exceeded the maximum allowable sync offset. + if (timeDelta > MAX_SYNC_OFFSET) + { + slave.IsCatchingUp = true; + Logger.Log($"Slave {i} catchup started (too far behind, delta = {timeDelta})"); + } + } + } + } + + /// + /// Updates the master clock's running state. + /// + private void updateMasterClock() + { + bool anyInSync = slaves.Any(s => !s.IsCatchingUp); + + if (Master.IsRunning != anyInSync) + { + if (anyInSync) + Master.Start(); + else + Master.Stop(); + } + } + } +} From 33ad7850cb432a2f2d7cfbbe60ae302f353443a4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 16:45:59 +0900 Subject: [PATCH 027/708] Remove LastFrameTime --- osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs | 4 ---- .../OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs | 2 -- 2 files changed, 6 deletions(-) diff --git a/osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs b/osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs index 2a6dfa9c8d..6ee867c6f8 100644 --- a/osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs +++ b/osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs @@ -167,10 +167,6 @@ namespace osu.Game.Tests.OnlinePlay public readonly Bindable WaitingOnFrames = new Bindable(true); IBindable IMultiplayerSlaveClock.WaitingOnFrames => WaitingOnFrames; - public double LastFrameTime => 0; - - double IMultiplayerSlaveClock.LastFrameTime => LastFrameTime; - public bool IsCatchingUp { get; set; } public readonly int Id; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs index e0fca45bdd..e90eed68ab 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs @@ -10,8 +10,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { IBindable WaitingOnFrames { get; } - double LastFrameTime { get; } - bool IsCatchingUp { get; set; } } } From fe3ba2b80ee1ad0034217ec1693646a6f5d31d95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 19:07:25 +0900 Subject: [PATCH 028/708] Implement IAdjustableClock on GameplayClockContainer --- .../Screens/Play/GameplayClockContainer.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6d863f0094..bbffdc2325 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -9,7 +10,7 @@ using osu.Framework.Timing; namespace osu.Game.Screens.Play { - public abstract class GameplayClockContainer : Container + public abstract class GameplayClockContainer : Container, IAdjustableClock { /// /// The final clock which is exposed to underlying components. @@ -90,5 +91,37 @@ namespace osu.Game.Screens.Play protected virtual IFrameBasedClock ClockToProcess => AdjustableClock; protected abstract GameplayClock CreateGameplayClock(IFrameBasedClock source); + + #region IAdjustableClock + + bool IAdjustableClock.Seek(double position) + { + Seek(position); + return true; + } + + void IAdjustableClock.Reset() + { + Restart(); + Stop(); + } + + public void ResetSpeedAdjustments() + { + } + + double IAdjustableClock.Rate + { + get => GameplayClock.Rate; + set => throw new NotSupportedException(); + } + + double IClock.Rate => GameplayClock.Rate; + + public double CurrentTime => GameplayClock.CurrentTime; + + public bool IsRunning => GameplayClock.IsRunning; + + #endregion } } From 1705d472b5ff9a6194e0b18ddae14f01c1b91286 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 19:12:52 +0900 Subject: [PATCH 029/708] Reimplement multiplayer syncing using new master/slave clocks --- .../TestSceneMultiplayerSpectator.cs | 30 +---- .../Spectate/MultiplayerSlaveClock.cs | 78 +++++++++++++ .../Spectate/MultiplayerSpectator.cs | 86 +++------------ .../Spectate/MultiplayerSpectatorPlayer.cs | 9 +- .../Spectate/MultiplayerSyncManager.cs | 5 - .../Multiplayer/Spectate/PlayerInstance.cs | 103 +----------------- 6 files changed, 104 insertions(+), 207 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSlaveClock.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs index 1ac012c0b5..ab6324df2d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs @@ -1,15 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; @@ -156,27 +153,6 @@ namespace osu.Game.Tests.Visual.Multiplayer checkPausedInstant(55, false); } - [Test] - public void TestPlayerStartsCatchingUpOnlyAfterExceedingMaxOffset() - { - start(new[] { 55, 56 }); - loadSpectateScreen(); - - sendFrames(55, 1000); - sendFrames(56, 1000); - - Bindable slowDownAdjustment; - - AddStep("slow down player 2", () => - { - slowDownAdjustment = new Bindable(0.99); - getInstance(56).Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, slowDownAdjustment); - }); - - AddUntilStep("exceeded min offset but not catching up", () => getGameplayOffset(55, 56) > PlayerInstance.MAX_OFFSET && !getInstance(56).IsCatchingUp); - AddUntilStep("catching up or caught up", () => getInstance(56).IsCatchingUp || Math.Abs(getGameplayOffset(55, 56)) < PlayerInstance.SYNC_TARGET * 2); - } - [Test] public void TestPlayersCatchUpAfterFallingBehind() { @@ -184,7 +160,7 @@ namespace osu.Game.Tests.Visual.Multiplayer loadSpectateScreen(); // Send initial frames for both players. A few more for player 1. - sendFrames(55, 100); + sendFrames(55, 1000); sendFrames(56, 10); checkPausedInstant(55, false); checkPausedInstant(56, false); @@ -194,11 +170,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddWaitStep("wait a few more frames", 10); // Send more frames for player 2. It should unpause. - sendFrames(56, 100); + sendFrames(56, 1000); checkPausedInstant(56, false); // Player 2 should catch up to player 1 after unpausing. - AddUntilStep("player 2 not catching up", () => !getInstance(56).IsCatchingUp); + AddUntilStep("player 2 not catching up", () => !getInstance(56).GameplayClock.IsCatchingUp); AddWaitStep("wait a bit", 10); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSlaveClock.cs new file mode 100644 index 0000000000..84a87271a2 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSlaveClock.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Timing; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public class MultiplayerSlaveClock : IFrameBasedClock, IMultiplayerSlaveClock + { + /// + /// The catchup rate. + /// + public const double CATCHUP_RATE = 2; + + private readonly IFrameBasedClock masterClock; + + public MultiplayerSlaveClock(IFrameBasedClock masterClock) + { + this.masterClock = masterClock; + } + + public double CurrentTime { get; private set; } + + public bool IsRunning { get; private set; } + + public void Reset() => CurrentTime = 0; + + public void Start() => IsRunning = true; + + public void Stop() => IsRunning = false; + + public bool Seek(double position) => true; + + public void ResetSpeedAdjustments() + { + } + + public double Rate => IsCatchingUp ? CATCHUP_RATE : 1; + + double IAdjustableClock.Rate + { + get => Rate; + set => throw new NotSupportedException(); + } + + double IClock.Rate => Rate; + + public void ProcessFrame() + { + masterClock.ProcessFrame(); + + ElapsedFrameTime = 0; + FramesPerSecond = 0; + + if (IsRunning) + { + double elapsedSource = masterClock.ElapsedFrameTime; + double elapsed = elapsedSource * Rate; + + CurrentTime += elapsed; + ElapsedFrameTime = elapsed; + FramesPerSecond = masterClock.FramesPerSecond; + } + } + + public double ElapsedFrameTime { get; private set; } + + public double FramesPerSecond { get; private set; } + + public FrameTimeInfo TimeInfo => new FrameTimeInfo { Elapsed = ElapsedFrameTime, Current = CurrentTime }; + + public IBindable WaitingOnFrames { get; } = new Bindable(); + + public bool IsCatchingUp { get; set; } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 931d13942b..be5a8cf8c0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -2,14 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; -using osu.Framework.Utils; using osu.Game.Online.Spectator; using osu.Game.Screens.Play; using osu.Game.Screens.Spectate; @@ -18,9 +14,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiplayerSpectator : SpectatorScreen { - private const double min_duration_to_allow_playback = 50; - private const double maximum_start_delay = 15000; - // Isolates beatmap/ruleset to this screen. public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -30,10 +23,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private SpectatorStreamingClient spectatorClient { get; set; } private readonly PlayerInstance[] instances; - private GameplayClockContainer gameplayClockContainer; + private MasterGameplayClockContainer masterClockContainer; + private IMultiplayerSyncManager syncManager; private PlayerGrid grid; private MultiplayerSpectatorLeaderboard leaderboard; - private double? loadFinishTime; public MultiplayerSpectator(int[] userIds) : base(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) @@ -46,7 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { Container leaderboardContainer; - InternalChild = gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0) + masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0) { Child = new GridContainer { @@ -70,6 +63,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } }; + InternalChildren = new[] + { + (Drawable)(syncManager = new MultiplayerSyncManager(masterClockContainer)), + masterClockContainer + }; + // Todo: This is not quite correct - it should be per-user to adjust for other mod combinations. var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor(); @@ -78,65 +77,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate LoadComponentAsync(leaderboard = new MultiplayerSpectatorLeaderboard(scoreProcessor, UserIds) { Expanded = { Value = true } }, leaderboardContainer.Add); } - protected override void UpdateAfterChildren() + protected override void LoadComplete() { - base.UpdateAfterChildren(); + base.LoadComplete(); - if (AllPlayersLoaded) - loadFinishTime ??= Time.Current; - - updateGameplayPlayingState(); - } - - private bool canStartGameplay => - // All players must be loaded, and... - AllPlayersLoaded - && ( - // All players have frames... - instances.All(i => i.Score.Replay.Frames.Count > 0) - // Or any player has frames and the maximum start delay has been exceeded. - || (Time.Current - loadFinishTime > maximum_start_delay - && instances.Any(i => i.Score.Replay.Frames.Count > 0)) - ); - - private bool firstStartFrame = true; - - private void updateGameplayPlayingState() - { - // Make sure all players are loaded and have frames before starting any. - if (!canStartGameplay) - { - foreach (var inst in instances) - inst?.PauseGameplay(); - return; - } - - if (firstStartFrame) - gameplayClockContainer.Restart(); - - // Not all instances may be in a valid gameplay state (see canStartGameplay). Only control the ones that are. - IEnumerable validInstances = instances.Where(i => i.Score.Replay.Frames.Count > 0); - - double targetGameplayTime = gameplayClockContainer.GameplayClock.CurrentTime; - - var instanceTimes = string.Join(',', validInstances.Select(i => $" {i.User.Id}: {(int)i.GetCurrentGameplayTime()}")); - Logger.Log($"target: {(int)targetGameplayTime},{instanceTimes}"); - - foreach (var inst in validInstances) - { - Debug.Assert(inst != null); - - double lastFrameTime = inst.Score.Replay.Frames.Select(f => f.Time).Last(); - double currentTime = inst.GetCurrentGameplayTime(); - - bool canContinuePlayback = Precision.DefinitelyBigger(lastFrameTime, currentTime, min_duration_to_allow_playback); - if (!canContinuePlayback) - continue; - - inst.ContinueGameplay(targetGameplayTime); - } - - firstStartFrame = false; + masterClockContainer.Stop(); + masterClockContainer.Restart(); } protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) @@ -151,15 +97,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (existingInstance != null) { grid.Remove(existingInstance); + syncManager.RemoveSlave(existingInstance.GameplayClock); leaderboard.RemoveClock(existingInstance.User.Id); } - LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score, gameplayClockContainer.GameplayClock), d => + LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score, new MultiplayerSlaveClock(masterClockContainer.GameplayClock)), d => { if (instances[userIndex] == d) { grid.Add(d); - leaderboard.AddClock(d.User.Id, d.Beatmap.Track); + syncManager.AddSlave(d.GameplayClock); + leaderboard.AddClock(d.User.Id, d.GameplayClock); } }); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index abb7ec83d8..c82c407c2c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -4,7 +4,6 @@ using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -12,13 +11,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiplayerSpectatorPlayer : SpectatorPlayer { - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + private readonly MultiplayerSlaveClock gameplayClock; - public new SubGameplayClockContainer GameplayClockContainer => (SubGameplayClockContainer)base.GameplayClockContainer; - - private readonly GameplayClock gameplayClock; - - public MultiplayerSpectatorPlayer(Score score, GameplayClock gameplayClock) + public MultiplayerSpectatorPlayer(Score score, MultiplayerSlaveClock gameplayClock) : base(score) { this.gameplayClock = gameplayClock; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs index 42f6536e90..8cbfa823cf 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs @@ -26,11 +26,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public const double MAXIMUM_START_DELAY = 15000; - /// - /// The catchup rate. - /// - public const double CATCHUP_RATE = 2; - /// /// The master clock which is used to control the timing of all slave clocks. /// diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 724a685cb7..631ac2d52e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -14,21 +13,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class PlayerInstance : CompositeDrawable { - /// - /// The rate at which a user catches up after becoming desynchronised. - /// - private const double catchup_rate = 2; - - /// - /// The offset from the expected time at which to START synchronisation. - /// - public const double MAX_OFFSET = 50; - - /// - /// The maximum offset from the expected time at which to STOP synchronisation. - /// - public const double SYNC_TARGET = 16; - public bool PlayerLoaded => stack?.CurrentScreen is Player; public User User => Score.ScoreInfo.User; @@ -36,17 +20,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public WorkingBeatmap Beatmap { get; private set; } public readonly Score Score; - private readonly GameplayClock gameplayClock; - - public bool IsCatchingUp { get; private set; } + public readonly MultiplayerSlaveClock GameplayClock; private OsuScreenStack stack; - private MultiplayerSpectatorPlayer player; - public PlayerInstance(Score score, GameplayClock gameplayClock) + public PlayerInstance(Score score, MultiplayerSlaveClock gameplayClock) { Score = score; - this.gameplayClock = gameplayClock; + GameplayClock = gameplayClock; RelativeSizeAxes = Axes.Both; Masking = true; @@ -67,83 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } }; - stack.Push(new MultiplayerSpectatorPlayerLoader(Score, () => player = new MultiplayerSpectatorPlayer(Score, gameplayClock))); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - updateCatchup(); - } - - private double targetGameplayTime; - - private void updateCatchup() - { - if (player?.IsLoaded != true) - return; - - if (Score.Replay.Frames.Count == 0) - return; - - if (player.GameplayClockContainer.IsPaused.Value) - return; - - double currentTime = Beatmap.Track.CurrentTime; - double timeBehind = targetGameplayTime - currentTime; - - double offsetForCatchup = IsCatchingUp ? SYNC_TARGET : MAX_OFFSET; - bool catchupRequired = timeBehind > offsetForCatchup; - - // Skip catchup if no work needs to be done. - if (catchupRequired == IsCatchingUp) - return; - - if (catchupRequired) - { - // player.GameplayClockContainer.AdjustableClock.Rate = catchup_rate; - Logger.Log($"{User.Id} catchup started (behind: {(int)timeBehind})"); - } - else - { - // player.GameplayClockContainer.AdjustableClock.Rate = 1; - Logger.Log($"{User.Id} catchup finished (behind: {(int)timeBehind})"); - } - - IsCatchingUp = catchupRequired; - } - - public double GetCurrentGameplayTime() - { - if (player?.IsLoaded != true) - return 0; - - return player.GameplayClockContainer.GameplayClock.CurrentTime; - } - - public bool IsPlaying() - { - if (player.IsLoaded != true) - return false; - - return player.GameplayClockContainer.GameplayClock.IsRunning; - } - - public void ContinueGameplay(double targetGameplayTime) - { - if (player?.IsLoaded != true) - return; - - player.GameplayClockContainer.Start(); - this.targetGameplayTime = targetGameplayTime; - } - - public void PauseGameplay() - { - if (player?.IsLoaded != true) - return; - - player.GameplayClockContainer.Stop(); + stack.Push(new MultiplayerSpectatorPlayerLoader(Score, () => new MultiplayerSpectatorPlayer(Score, GameplayClock))); } // Player interferes with global input, so disable input for now. From df4fce2c570b885176cb70f1f2232610ad2a7848 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 19:16:00 +0900 Subject: [PATCH 030/708] Rename classes --- ...s => MultiplayerCatchupSyncManagerTest.cs} | 50 +++++++++---------- ....cs => IMultiplayerSpectatorSlaveClock.cs} | 2 +- ...cs => IMultiplayerSpectatorSyncManager.cs} | 6 +-- ...er.cs => MultiplayerCatchupSyncManager.cs} | 10 ++-- .../Spectate/MultiplayerSpectator.cs | 6 +-- .../Spectate/MultiplayerSpectatorPlayer.cs | 4 +- ...k.cs => MultiplayerSpectatorSlaveClock.cs} | 4 +- .../Multiplayer/Spectate/PlayerInstance.cs | 4 +- 8 files changed, 43 insertions(+), 43 deletions(-) rename osu.Game.Tests/OnlinePlay/{MultiplayerSyncManagerTest.cs => MultiplayerCatchupSyncManagerTest.cs} (70%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{IMultiplayerSlaveClock.cs => IMultiplayerSpectatorSlaveClock.cs} (83%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{IMultiplayerSyncManager.cs => IMultiplayerSpectatorSyncManager.cs} (62%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{MultiplayerSyncManager.cs => MultiplayerCatchupSyncManager.cs} (91%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{MultiplayerSlaveClock.cs => MultiplayerSpectatorSlaveClock.cs} (91%) diff --git a/osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs b/osu.Game.Tests/OnlinePlay/MultiplayerCatchupSyncManagerTest.cs similarity index 70% rename from osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs rename to osu.Game.Tests/OnlinePlay/MultiplayerCatchupSyncManagerTest.cs index 6ee867c6f8..f15fb5cfd1 100644 --- a/osu.Game.Tests/OnlinePlay/MultiplayerSyncManagerTest.cs +++ b/osu.Game.Tests/OnlinePlay/MultiplayerCatchupSyncManagerTest.cs @@ -12,22 +12,22 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.OnlinePlay { [HeadlessTest] - public class MultiplayerSyncManagerTest : OsuTestScene + public class MultiplayerCatchupSyncManagerTest : OsuTestScene { private TestManualClock master; - private MultiplayerSyncManager syncManager; + private MultiplayerCatchupSyncManager catchupSyncManager; - private TestSlaveClock slave1; - private TestSlaveClock slave2; + private TestSpectatorSlaveClock slave1; + private TestSpectatorSlaveClock slave2; [SetUp] public void Setup() { - syncManager = new MultiplayerSyncManager(master = new TestManualClock()); - syncManager.AddSlave(slave1 = new TestSlaveClock(1)); - syncManager.AddSlave(slave2 = new TestSlaveClock(2)); + catchupSyncManager = new MultiplayerCatchupSyncManager(master = new TestManualClock()); + catchupSyncManager.AddSlave(slave1 = new TestSpectatorSlaveClock(1)); + catchupSyncManager.AddSlave(slave2 = new TestSpectatorSlaveClock(2)); - Schedule(() => Child = syncManager); + Schedule(() => Child = catchupSyncManager); } [Test] @@ -47,7 +47,7 @@ namespace osu.Game.Tests.OnlinePlay [Test] public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime() { - AddWaitStep($"wait {MultiplayerSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(MultiplayerSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + AddWaitStep($"wait {MultiplayerCatchupSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(MultiplayerCatchupSyncManager.MAXIMUM_START_DELAY / TimePerAction)); assertMasterState(false); } @@ -55,7 +55,7 @@ namespace osu.Game.Tests.OnlinePlay public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime() { setWaiting(() => slave1, false); - AddWaitStep($"wait {MultiplayerSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(MultiplayerSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + AddWaitStep($"wait {MultiplayerCatchupSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(MultiplayerCatchupSyncManager.MAXIMUM_START_DELAY / TimePerAction)); assertMasterState(true); } @@ -64,7 +64,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(MultiplayerSyncManager.SYNC_TARGET + 1); + setMasterTime(MultiplayerCatchupSyncManager.SYNC_TARGET + 1); assertCatchingUp(() => slave1, false); } @@ -73,7 +73,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(MultiplayerSyncManager.MAX_SYNC_OFFSET + 1); + setMasterTime(MultiplayerCatchupSyncManager.MAX_SYNC_OFFSET + 1); assertCatchingUp(() => slave1, true); assertCatchingUp(() => slave2, true); } @@ -83,8 +83,8 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(MultiplayerSyncManager.MAX_SYNC_OFFSET + 1); - setSlaveTime(() => slave1, MultiplayerSyncManager.SYNC_TARGET + 1); + setMasterTime(MultiplayerCatchupSyncManager.MAX_SYNC_OFFSET + 1); + setSlaveTime(() => slave1, MultiplayerCatchupSyncManager.SYNC_TARGET + 1); assertCatchingUp(() => slave1, true); } @@ -93,8 +93,8 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(MultiplayerSyncManager.MAX_SYNC_OFFSET + 2); - setSlaveTime(() => slave1, MultiplayerSyncManager.SYNC_TARGET); + setMasterTime(MultiplayerCatchupSyncManager.MAX_SYNC_OFFSET + 2); + setSlaveTime(() => slave1, MultiplayerCatchupSyncManager.SYNC_TARGET); assertCatchingUp(() => slave1, false); assertCatchingUp(() => slave2, true); } @@ -104,7 +104,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setSlaveTime(() => slave1, -MultiplayerSyncManager.SYNC_TARGET); + setSlaveTime(() => slave1, -MultiplayerCatchupSyncManager.SYNC_TARGET); assertCatchingUp(() => slave1, false); assertSlaveState(() => slave1, true); } @@ -114,7 +114,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setSlaveTime(() => slave1, -MultiplayerSyncManager.SYNC_TARGET - 1); + setSlaveTime(() => slave1, -MultiplayerCatchupSyncManager.SYNC_TARGET - 1); // This is a silent catchup, where IsCatchingUp = false but IsRunning = false also. assertCatchingUp(() => slave1, false); @@ -135,7 +135,7 @@ namespace osu.Game.Tests.OnlinePlay assertSlaveState(() => slave1, false); } - private void setWaiting(Func slave, bool waiting) + private void setWaiting(Func slave, bool waiting) => AddStep($"set slave {slave().Id} waiting = {waiting}", () => slave().WaitingOnFrames.Value = waiting); private void setAllWaiting(bool waiting) => AddStep($"set all slaves waiting = {waiting}", () => @@ -150,28 +150,28 @@ namespace osu.Game.Tests.OnlinePlay /// /// slave.Time = master.Time - offsetFromMaster /// - private void setSlaveTime(Func slave, double offsetFromMaster) + private void setSlaveTime(Func slave, double offsetFromMaster) => AddStep($"set slave {slave().Id} = master - {offsetFromMaster}", () => slave().Seek(master.CurrentTime - offsetFromMaster)); private void assertMasterState(bool running) => AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running); - private void assertCatchingUp(Func slave, bool catchingUp) => + private void assertCatchingUp(Func slave, bool catchingUp) => AddAssert($"slave {slave().Id} {(catchingUp ? "is" : "is not")} catching up", () => slave().IsCatchingUp == catchingUp); - private void assertSlaveState(Func slave, bool running) + private void assertSlaveState(Func slave, bool running) => AddAssert($"slave {slave().Id} {(running ? "is" : "is not")} running", () => slave().IsRunning == running); - private class TestSlaveClock : TestManualClock, IMultiplayerSlaveClock + private class TestSpectatorSlaveClock : TestManualClock, IMultiplayerSpectatorSlaveClock { public readonly Bindable WaitingOnFrames = new Bindable(true); - IBindable IMultiplayerSlaveClock.WaitingOnFrames => WaitingOnFrames; + IBindable IMultiplayerSpectatorSlaveClock.WaitingOnFrames => WaitingOnFrames; public bool IsCatchingUp { get; set; } public readonly int Id; - public TestSlaveClock(int id) + public TestSpectatorSlaveClock(int id) { Id = id; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSlaveClock.cs similarity index 83% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSlaveClock.cs index e90eed68ab..fe2c2dfec9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSlaveClock.cs @@ -6,7 +6,7 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public interface IMultiplayerSlaveClock : IAdjustableClock + public interface IMultiplayerSpectatorSlaveClock : IAdjustableClock { IBindable WaitingOnFrames { get; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSyncManager.cs similarity index 62% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSyncManager.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSyncManager.cs index 2c50596823..5643bde68f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSyncManager.cs @@ -5,12 +5,12 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public interface IMultiplayerSyncManager + public interface IMultiplayerSpectatorSyncManager { IAdjustableClock Master { get; } - void AddSlave(IMultiplayerSlaveClock clock); + void AddSlave(IMultiplayerSpectatorSlaveClock clock); - void RemoveSlave(IMultiplayerSlaveClock clock); + void RemoveSlave(IMultiplayerSpectatorSlaveClock clock); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerCatchupSyncManager.cs similarity index 91% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerCatchupSyncManager.cs index 8cbfa823cf..873f34626d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerCatchupSyncManager.cs @@ -9,7 +9,7 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public class MultiplayerSyncManager : Component, IMultiplayerSyncManager + public class MultiplayerCatchupSyncManager : Component, IMultiplayerSpectatorSyncManager { /// /// The offset from the master clock to which slaves should be synchronised to. @@ -34,19 +34,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// The slave clocks. /// - private readonly List slaves = new List(); + private readonly List slaves = new List(); private bool hasStarted; private double? firstStartAttemptTime; - public MultiplayerSyncManager(IAdjustableClock master) + public MultiplayerCatchupSyncManager(IAdjustableClock master) { Master = master; } - public void AddSlave(IMultiplayerSlaveClock clock) => slaves.Add(clock); + public void AddSlave(IMultiplayerSpectatorSlaveClock clock) => slaves.Add(clock); - public void RemoveSlave(IMultiplayerSlaveClock clock) => slaves.Remove(clock); + public void RemoveSlave(IMultiplayerSpectatorSlaveClock clock) => slaves.Remove(clock); protected override void Update() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index be5a8cf8c0..3dade4d32c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly PlayerInstance[] instances; private MasterGameplayClockContainer masterClockContainer; - private IMultiplayerSyncManager syncManager; + private IMultiplayerSpectatorSyncManager syncManager; private PlayerGrid grid; private MultiplayerSpectatorLeaderboard leaderboard; @@ -65,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate InternalChildren = new[] { - (Drawable)(syncManager = new MultiplayerSyncManager(masterClockContainer)), + (Drawable)(syncManager = new MultiplayerCatchupSyncManager(masterClockContainer)), masterClockContainer }; @@ -101,7 +101,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate leaderboard.RemoveClock(existingInstance.User.Id); } - LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score, new MultiplayerSlaveClock(masterClockContainer.GameplayClock)), d => + LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score, new MultiplayerSpectatorSlaveClock(masterClockContainer.GameplayClock)), d => { if (instances[userIndex] == d) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index c82c407c2c..7fe1416224 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -11,9 +11,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiplayerSpectatorPlayer : SpectatorPlayer { - private readonly MultiplayerSlaveClock gameplayClock; + private readonly MultiplayerSpectatorSlaveClock gameplayClock; - public MultiplayerSpectatorPlayer(Score score, MultiplayerSlaveClock gameplayClock) + public MultiplayerSpectatorPlayer(Score score, MultiplayerSpectatorSlaveClock gameplayClock) : base(score) { this.gameplayClock = gameplayClock; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorSlaveClock.cs similarity index 91% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSlaveClock.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorSlaveClock.cs index 84a87271a2..6960d24295 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorSlaveClock.cs @@ -7,7 +7,7 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public class MultiplayerSlaveClock : IFrameBasedClock, IMultiplayerSlaveClock + public class MultiplayerSpectatorSlaveClock : IFrameBasedClock, IMultiplayerSpectatorSlaveClock { /// /// The catchup rate. @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly IFrameBasedClock masterClock; - public MultiplayerSlaveClock(IFrameBasedClock masterClock) + public MultiplayerSpectatorSlaveClock(IFrameBasedClock masterClock) { this.masterClock = masterClock; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 631ac2d52e..384c1f1f2b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -20,11 +20,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public WorkingBeatmap Beatmap { get; private set; } public readonly Score Score; - public readonly MultiplayerSlaveClock GameplayClock; + public readonly MultiplayerSpectatorSlaveClock GameplayClock; private OsuScreenStack stack; - public PlayerInstance(Score score, MultiplayerSlaveClock gameplayClock) + public PlayerInstance(Score score, MultiplayerSpectatorSlaveClock gameplayClock) { Score = score; GameplayClock = gameplayClock; From 82fcabb8f0d94b083b25306d1647c1925c598f5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 19:32:55 +0900 Subject: [PATCH 031/708] More refactorings/renamespacings/xmldocs --- ...s => MultiplayerCatchUpSyncManagerTest.cs} | 48 +++++++++++-------- .../IMultiplayerSpectatorSlaveClock.cs | 15 ------ .../IMultiplayerSpectatorSyncManager.cs | 16 ------- .../Spectate/MultiplayerSpectator.cs | 7 +-- .../Spectate/MultiplayerSpectatorPlayer.cs | 5 +- .../Multiplayer/Spectate/PlayerInstance.cs | 5 +- .../Spectate/Sync/ISpectatorSlaveClock.cs | 24 ++++++++++ .../Spectate/Sync/ISpectatorSyncManager.cs | 30 ++++++++++++ .../SpectatorCatchUpSlaveClock.cs} | 6 +-- .../SpectatorCatchUpSyncManager.cs} | 15 +++--- 10 files changed, 105 insertions(+), 66 deletions(-) rename osu.Game.Tests/OnlinePlay/{MultiplayerCatchupSyncManagerTest.cs => MultiplayerCatchUpSyncManagerTest.cs} (76%) delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSlaveClock.cs delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSyncManager.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSlaveClock.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{MultiplayerSpectatorSlaveClock.cs => Sync/SpectatorCatchUpSlaveClock.cs} (89%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{MultiplayerCatchupSyncManager.cs => Sync/SpectatorCatchUpSyncManager.cs} (88%) diff --git a/osu.Game.Tests/OnlinePlay/MultiplayerCatchupSyncManagerTest.cs b/osu.Game.Tests/OnlinePlay/MultiplayerCatchUpSyncManagerTest.cs similarity index 76% rename from osu.Game.Tests/OnlinePlay/MultiplayerCatchupSyncManagerTest.cs rename to osu.Game.Tests/OnlinePlay/MultiplayerCatchUpSyncManagerTest.cs index f15fb5cfd1..6aa1219d53 100644 --- a/osu.Game.Tests/OnlinePlay/MultiplayerCatchupSyncManagerTest.cs +++ b/osu.Game.Tests/OnlinePlay/MultiplayerCatchUpSyncManagerTest.cs @@ -6,16 +6,16 @@ using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Tests.Visual; namespace osu.Game.Tests.OnlinePlay { [HeadlessTest] - public class MultiplayerCatchupSyncManagerTest : OsuTestScene + public class MultiplayerCatchUpSyncManagerTest : OsuTestScene { private TestManualClock master; - private MultiplayerCatchupSyncManager catchupSyncManager; + private SpectatorCatchUpSyncManager syncManager; private TestSpectatorSlaveClock slave1; private TestSpectatorSlaveClock slave2; @@ -23,11 +23,11 @@ namespace osu.Game.Tests.OnlinePlay [SetUp] public void Setup() { - catchupSyncManager = new MultiplayerCatchupSyncManager(master = new TestManualClock()); - catchupSyncManager.AddSlave(slave1 = new TestSpectatorSlaveClock(1)); - catchupSyncManager.AddSlave(slave2 = new TestSpectatorSlaveClock(2)); + syncManager = new SpectatorCatchUpSyncManager(master = new TestManualClock()); + syncManager.AddSlave(slave1 = new TestSpectatorSlaveClock(1)); + syncManager.AddSlave(slave2 = new TestSpectatorSlaveClock(2)); - Schedule(() => Child = catchupSyncManager); + Schedule(() => Child = syncManager); } [Test] @@ -47,7 +47,7 @@ namespace osu.Game.Tests.OnlinePlay [Test] public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime() { - AddWaitStep($"wait {MultiplayerCatchupSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(MultiplayerCatchupSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + AddWaitStep($"wait {SpectatorCatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(SpectatorCatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); assertMasterState(false); } @@ -55,7 +55,7 @@ namespace osu.Game.Tests.OnlinePlay public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime() { setWaiting(() => slave1, false); - AddWaitStep($"wait {MultiplayerCatchupSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(MultiplayerCatchupSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + AddWaitStep($"wait {SpectatorCatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(SpectatorCatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); assertMasterState(true); } @@ -64,7 +64,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(MultiplayerCatchupSyncManager.SYNC_TARGET + 1); + setMasterTime(SpectatorCatchUpSyncManager.SYNC_TARGET + 1); assertCatchingUp(() => slave1, false); } @@ -73,7 +73,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(MultiplayerCatchupSyncManager.MAX_SYNC_OFFSET + 1); + setMasterTime(SpectatorCatchUpSyncManager.MAX_SYNC_OFFSET + 1); assertCatchingUp(() => slave1, true); assertCatchingUp(() => slave2, true); } @@ -83,8 +83,8 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(MultiplayerCatchupSyncManager.MAX_SYNC_OFFSET + 1); - setSlaveTime(() => slave1, MultiplayerCatchupSyncManager.SYNC_TARGET + 1); + setMasterTime(SpectatorCatchUpSyncManager.MAX_SYNC_OFFSET + 1); + setSlaveTime(() => slave1, SpectatorCatchUpSyncManager.SYNC_TARGET + 1); assertCatchingUp(() => slave1, true); } @@ -93,8 +93,8 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(MultiplayerCatchupSyncManager.MAX_SYNC_OFFSET + 2); - setSlaveTime(() => slave1, MultiplayerCatchupSyncManager.SYNC_TARGET); + setMasterTime(SpectatorCatchUpSyncManager.MAX_SYNC_OFFSET + 2); + setSlaveTime(() => slave1, SpectatorCatchUpSyncManager.SYNC_TARGET); assertCatchingUp(() => slave1, false); assertCatchingUp(() => slave2, true); } @@ -104,7 +104,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setSlaveTime(() => slave1, -MultiplayerCatchupSyncManager.SYNC_TARGET); + setSlaveTime(() => slave1, -SpectatorCatchUpSyncManager.SYNC_TARGET); assertCatchingUp(() => slave1, false); assertSlaveState(() => slave1, true); } @@ -114,7 +114,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setSlaveTime(() => slave1, -MultiplayerCatchupSyncManager.SYNC_TARGET - 1); + setSlaveTime(() => slave1, -SpectatorCatchUpSyncManager.SYNC_TARGET - 1); // This is a silent catchup, where IsCatchingUp = false but IsRunning = false also. assertCatchingUp(() => slave1, false); @@ -162,10 +162,10 @@ namespace osu.Game.Tests.OnlinePlay private void assertSlaveState(Func slave, bool running) => AddAssert($"slave {slave().Id} {(running ? "is" : "is not")} running", () => slave().IsRunning == running); - private class TestSpectatorSlaveClock : TestManualClock, IMultiplayerSpectatorSlaveClock + private class TestSpectatorSlaveClock : TestManualClock, ISpectatorSlaveClock { public readonly Bindable WaitingOnFrames = new Bindable(true); - IBindable IMultiplayerSpectatorSlaveClock.WaitingOnFrames => WaitingOnFrames; + IBindable ISpectatorSlaveClock.WaitingOnFrames => WaitingOnFrames; public bool IsCatchingUp { get; set; } @@ -183,6 +183,16 @@ namespace osu.Game.Tests.OnlinePlay Start(); }); } + + public void ProcessFrame() + { + } + + public double ElapsedFrameTime => 0; + + public double FramesPerSecond => 0; + + public FrameTimeInfo TimeInfo => default; } private class TestManualClock : ManualClock, IAdjustableClock diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSlaveClock.cs deleted file mode 100644 index fe2c2dfec9..0000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSlaveClock.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Bindables; -using osu.Framework.Timing; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate -{ - public interface IMultiplayerSpectatorSlaveClock : IAdjustableClock - { - IBindable WaitingOnFrames { get; } - - bool IsCatchingUp { get; set; } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSyncManager.cs deleted file mode 100644 index 5643bde68f..0000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/IMultiplayerSpectatorSyncManager.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Timing; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate -{ - public interface IMultiplayerSpectatorSyncManager - { - IAdjustableClock Master { get; } - - void AddSlave(IMultiplayerSpectatorSlaveClock clock); - - void RemoveSlave(IMultiplayerSpectatorSlaveClock clock); - } -} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 3dade4d32c..441839b401 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Spectator; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; using osu.Game.Screens.Spectate; @@ -24,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly PlayerInstance[] instances; private MasterGameplayClockContainer masterClockContainer; - private IMultiplayerSpectatorSyncManager syncManager; + private ISpectatorSyncManager syncManager; private PlayerGrid grid; private MultiplayerSpectatorLeaderboard leaderboard; @@ -65,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate InternalChildren = new[] { - (Drawable)(syncManager = new MultiplayerCatchupSyncManager(masterClockContainer)), + (Drawable)(syncManager = new SpectatorCatchUpSyncManager(masterClockContainer)), masterClockContainer }; @@ -101,7 +102,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate leaderboard.RemoveClock(existingInstance.User.Id); } - LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score, new MultiplayerSpectatorSlaveClock(masterClockContainer.GameplayClock)), d => + LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score, new SpectatorCatchUpSlaveClock(masterClockContainer.GameplayClock)), d => { if (instances[userIndex] == d) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index 7fe1416224..1a7e0dd36a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -5,15 +5,16 @@ using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiplayerSpectatorPlayer : SpectatorPlayer { - private readonly MultiplayerSpectatorSlaveClock gameplayClock; + private readonly SpectatorCatchUpSlaveClock gameplayClock; - public MultiplayerSpectatorPlayer(Score score, MultiplayerSpectatorSlaveClock gameplayClock) + public MultiplayerSpectatorPlayer(Score score, SpectatorCatchUpSlaveClock gameplayClock) : base(score) { this.gameplayClock = gameplayClock; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 384c1f1f2b..401732f7fb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; using osu.Game.Users; @@ -20,11 +21,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public WorkingBeatmap Beatmap { get; private set; } public readonly Score Score; - public readonly MultiplayerSpectatorSlaveClock GameplayClock; + public readonly SpectatorCatchUpSlaveClock GameplayClock; private OsuScreenStack stack; - public PlayerInstance(Score score, MultiplayerSpectatorSlaveClock gameplayClock) + public PlayerInstance(Score score, SpectatorCatchUpSlaveClock gameplayClock) { Score = score; GameplayClock = gameplayClock; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSlaveClock.cs new file mode 100644 index 0000000000..d8733d4322 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSlaveClock.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.Bindables; +using osu.Framework.Timing; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync +{ + /// + /// A clock which is used by s and managed by an . + /// + public interface ISpectatorSlaveClock : IFrameBasedClock, IAdjustableClock + { + /// + /// Whether this clock is waiting on frames to continue playback. + /// + IBindable WaitingOnFrames { get; } + + /// + /// Whether this clock is resynchronising to the master clock. + /// + bool IsCatchingUp { get; set; } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs new file mode 100644 index 0000000000..107f9637a3 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Timing; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync +{ + /// + /// Manages the synchronisation between one or more slave clocks in relation to a master clock. + /// + public interface ISpectatorSyncManager + { + /// + /// The master clock which slaves should synchronise to. + /// + IAdjustableClock Master { get; } + + /// + /// Adds a slave clock. + /// + /// The clock to add. + void AddSlave(ISpectatorSlaveClock clock); + + /// + /// Removes a slave clock. + /// + /// The clock to remove. + void RemoveSlave(ISpectatorSlaveClock clock); + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSlaveClock.cs similarity index 89% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorSlaveClock.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSlaveClock.cs index 6960d24295..e4d25894c8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorSlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSlaveClock.cs @@ -5,9 +5,9 @@ using System; using osu.Framework.Bindables; using osu.Framework.Timing; -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { - public class MultiplayerSpectatorSlaveClock : IFrameBasedClock, IMultiplayerSpectatorSlaveClock + public class SpectatorCatchUpSlaveClock : ISpectatorSlaveClock { /// /// The catchup rate. @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly IFrameBasedClock masterClock; - public MultiplayerSpectatorSlaveClock(IFrameBasedClock masterClock) + public SpectatorCatchUpSlaveClock(IFrameBasedClock masterClock) { this.masterClock = masterClock; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerCatchupSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSyncManager.cs similarity index 88% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerCatchupSyncManager.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSyncManager.cs index 873f34626d..46e86177ca 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerCatchupSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSyncManager.cs @@ -7,9 +7,12 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Timing; -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { - public class MultiplayerCatchupSyncManager : Component, IMultiplayerSpectatorSyncManager + /// + /// A which synchronises de-synced slave clocks through catchup. + /// + public class SpectatorCatchUpSyncManager : Component, ISpectatorSyncManager { /// /// The offset from the master clock to which slaves should be synchronised to. @@ -34,19 +37,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// The slave clocks. /// - private readonly List slaves = new List(); + private readonly List slaves = new List(); private bool hasStarted; private double? firstStartAttemptTime; - public MultiplayerCatchupSyncManager(IAdjustableClock master) + public SpectatorCatchUpSyncManager(IAdjustableClock master) { Master = master; } - public void AddSlave(IMultiplayerSpectatorSlaveClock clock) => slaves.Add(clock); + public void AddSlave(ISpectatorSlaveClock clock) => slaves.Add(clock); - public void RemoveSlave(IMultiplayerSpectatorSlaveClock clock) => slaves.Remove(clock); + public void RemoveSlave(ISpectatorSlaveClock clock) => slaves.Remove(clock); protected override void Update() { From 33cc5c5cb36456479bacf67ccdb4e585ce8e7ca5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 19:35:57 +0900 Subject: [PATCH 032/708] A few more xmldocs --- .../Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs | 10 +++++----- .../Spectate/Sync/SpectatorCatchUpSlaveClock.cs | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs index 107f9637a3..5edbb3bc2c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs @@ -6,7 +6,7 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { /// - /// Manages the synchronisation between one or more slave clocks in relation to a master clock. + /// Manages the synchronisation between one or more s in relation to a master clock. /// public interface ISpectatorSyncManager { @@ -16,15 +16,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync IAdjustableClock Master { get; } /// - /// Adds a slave clock. + /// Adds an to manage. /// - /// The clock to add. + /// The to add. void AddSlave(ISpectatorSlaveClock clock); /// - /// Removes a slave clock. + /// Removes an , stopping it from being managed by this . /// - /// The clock to remove. + /// The to remove. void RemoveSlave(ISpectatorSlaveClock clock); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSlaveClock.cs index e4d25894c8..4b529021de 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSlaveClock.cs @@ -7,10 +7,13 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { + /// + /// A which catches up using rate adjustment. + /// public class SpectatorCatchUpSlaveClock : ISpectatorSlaveClock { /// - /// The catchup rate. + /// The catch up rate. /// public const double CATCHUP_RATE = 2; From b391a8f94e23b66a82825fc180a8fd10db0ffba3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 19:37:45 +0900 Subject: [PATCH 033/708] Properly bind WaitingOnFrames --- .../Spectate/MultiplayerSpectatorPlayer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index 1a7e0dd36a..19cc9f18ad 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -12,14 +13,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiplayerSpectatorPlayer : SpectatorPlayer { - private readonly SpectatorCatchUpSlaveClock gameplayClock; + private readonly ISpectatorSlaveClock gameplayClock; - public MultiplayerSpectatorPlayer(Score score, SpectatorCatchUpSlaveClock gameplayClock) + public MultiplayerSpectatorPlayer(Score score, ISpectatorSlaveClock gameplayClock) : base(score) { this.gameplayClock = gameplayClock; } + [BackgroundDependencyLoader] + private void load() + { + gameplayClock.WaitingOnFrames.BindTo(DrawableRuleset.FrameStableClock.WaitingOnFrames); + } + protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new SubGameplayClockContainer(gameplayClock); } From 25b8c2f257f8a52aba5761b819ea0ee7f62b86a4 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Wed, 14 Apr 2021 00:04:03 -0400 Subject: [PATCH 034/708] 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 035/708] 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 5ac0eb02cd0bffd1c7876178c5b8ad50bad3b165 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 12:25:29 +0900 Subject: [PATCH 036/708] Always add player instances at first, populate later --- .../TestSceneMultiplayerSpectator.cs | 46 ++++++++++++++----- .../Spectate/MultiplayerSpectator.cs | 27 ++++------- .../Multiplayer/Spectate/PlayerInstance.cs | 23 +++++----- 3 files changed, 56 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs index ab6324df2d..4026258830 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerSpectator : MultiplayerTestScene { [Cached(typeof(SpectatorStreamingClient))] - private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient(); + private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient(); [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestUserLookupCache(); @@ -62,18 +62,42 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add streaming client", () => { - Remove(testSpectatorStreamingClient); - Add(testSpectatorStreamingClient); + Remove(streamingClient); + Add(streamingClient); }); AddStep("finish previous gameplay", () => { foreach (var id in playingUserIds) - testSpectatorStreamingClient.EndPlay(id, importedBeatmapId); + streamingClient.EndPlay(id, importedBeatmapId); playingUserIds.Clear(); }); } + [Test] + public void TestDelayedStart() + { + AddStep("start players silently", () => + { + Client.CurrentMatchPlayingUserIds.Add(55); + Client.CurrentMatchPlayingUserIds.Add(56); + playingUserIds.Add(55); + playingUserIds.Add(56); + nextFrame[55] = 0; + nextFrame[56] = 0; + }); + + loadSpectateScreen(false); + + AddWaitStep("wait a bit", 10); + AddStep("load player 55", () => streamingClient.StartPlay(55, importedBeatmapId)); + AddUntilStep("one player added", () => spectator.ChildrenOfType().Count() == 1); + + AddWaitStep("wait a bit", 10); + AddStep("load player 56", () => streamingClient.StartPlay(56, importedBeatmapId)); + AddUntilStep("two players added", () => spectator.ChildrenOfType().Count() == 2); + } + [Test] public void TestGeneral() { @@ -178,7 +202,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddWaitStep("wait a bit", 10); } - private void loadSpectateScreen() + private void loadSpectateScreen(bool waitForPlayerLoad = true) { AddStep("load screen", () => { @@ -188,7 +212,7 @@ namespace osu.Game.Tests.Visual.Multiplayer LoadScreen(spectator = new MultiplayerSpectator(playingUserIds.ToArray())); }); - AddUntilStep("wait for screen load", () => spectator.LoadState == LoadState.Loaded && spectator.AllPlayersLoaded); + AddUntilStep("wait for screen load", () => spectator.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectator.AllPlayersLoaded)); } private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); @@ -200,7 +224,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int id in userIds) { Client.CurrentMatchPlayingUserIds.Add(id); - testSpectatorStreamingClient.StartPlay(id, beatmapId ?? importedBeatmapId); + streamingClient.StartPlay(id, beatmapId ?? importedBeatmapId); playingUserIds.Add(id); nextFrame[id] = 0; } @@ -211,7 +235,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("end play", () => { - testSpectatorStreamingClient.EndPlay(userId, beatmapId ?? importedBeatmapId); + streamingClient.EndPlay(userId, beatmapId ?? importedBeatmapId); playingUserIds.Remove(userId); nextFrame.Remove(userId); }); @@ -225,7 +249,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { foreach (int id in userIds) { - testSpectatorStreamingClient.SendFrames(id, nextFrame[id], count); + streamingClient.SendFrames(id, nextFrame[id], count); nextFrame[id] += count; } }); @@ -246,7 +270,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); - private PlayerInstance getInstance(int userId) => spectator.ChildrenOfType().Single(p => p.User.Id == userId); + private PlayerInstance getInstance(int userId) => spectator.ChildrenOfType().Single(p => p.UserId == userId); public class TestSpectatorStreamingClient : SpectatorStreamingClient { @@ -297,7 +321,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public override void WatchUser(int userId) { - if (userSentStateDictionary[userId]) + if (userSentStateDictionary.TryGetValue(userId, out var sent) && sent) { // usually the server would do this. sendState(userId, userBeatmapDictionary[userId]); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 441839b401..08b9938e79 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public MultiplayerSpectator(int[] userIds) : base(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) { - instances = new PlayerInstance[userIds.Length]; + instances = new PlayerInstance[UserIds.Length]; } [BackgroundDependencyLoader] @@ -70,6 +70,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate masterClockContainer }; + for (int i = 0; i < UserIds.Length; i++) + grid.Add(instances[i] = new PlayerInstance(UserIds[i], new SpectatorCatchUpSlaveClock(masterClockContainer.GameplayClock))); + // Todo: This is not quite correct - it should be per-user to adjust for other mod combinations. var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor(); @@ -93,24 +96,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void StartGameplay(int userId, GameplayState gameplayState) { int userIndex = getIndexForUser(userId); - var existingInstance = instances[userIndex]; - if (existingInstance != null) - { - grid.Remove(existingInstance); - syncManager.RemoveSlave(existingInstance.GameplayClock); - leaderboard.RemoveClock(existingInstance.User.Id); - } + var instance = instances[userIndex]; + syncManager.RemoveSlave(instance.GameplayClock); + leaderboard.RemoveClock(instance.UserId); - LoadComponentAsync(instances[userIndex] = new PlayerInstance(gameplayState.Score, new SpectatorCatchUpSlaveClock(masterClockContainer.GameplayClock)), d => - { - if (instances[userIndex] == d) - { - grid.Add(d); - syncManager.AddSlave(d.GameplayClock); - leaderboard.AddClock(d.User.Id, d.GameplayClock); - } - }); + instance.LoadPlayer(gameplayState.Score); + syncManager.AddSlave(instance.GameplayClock); + leaderboard.AddClock(instance.UserId, instance.GameplayClock); } protected override void EndGameplay(int userId) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 401732f7fb..8c54fc9ec2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -8,7 +8,6 @@ using osu.Game.Beatmaps; using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; -using osu.Game.Users; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { @@ -16,30 +15,30 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public bool PlayerLoaded => stack?.CurrentScreen is Player; - public User User => Score.ScoreInfo.User; - - public WorkingBeatmap Beatmap { get; private set; } - - public readonly Score Score; + public readonly int UserId; public readonly SpectatorCatchUpSlaveClock GameplayClock; + public Score Score { get; private set; } + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + private OsuScreenStack stack; - public PlayerInstance(Score score, SpectatorCatchUpSlaveClock gameplayClock) + public PlayerInstance(int userId, SpectatorCatchUpSlaveClock gameplayClock) { - Score = score; + UserId = userId; GameplayClock = gameplayClock; RelativeSizeAxes = Axes.Both; Masking = true; } - [BackgroundDependencyLoader] - private void load(BeatmapManager beatmapManager) + public void LoadPlayer(Score score) { - Beatmap = beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap, bypassCache: true); + Score = score; - InternalChild = new GameplayIsolationContainer(Beatmap, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) + InternalChild = new GameplayIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap, bypassCache: true), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, Child = new DrawSizePreservingFillContainer From 1c086d99deae8c03b1412b13ec6f5c00e0abfe94 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 13:28:32 +0900 Subject: [PATCH 037/708] Add loading spinner --- .../Spectate/MultiplayerSpectator.cs | 8 ++----- .../Multiplayer/Spectate/PlayerInstance.cs | 24 +++++++++++++------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 08b9938e79..e1af95b870 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -95,13 +95,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void StartGameplay(int userId, GameplayState gameplayState) { - int userIndex = getIndexForUser(userId); + var instance = instances[getIndexForUser(userId)]; + instance.LoadScore(gameplayState.Score); - var instance = instances[userIndex]; - syncManager.RemoveSlave(instance.GameplayClock); - leaderboard.RemoveClock(instance.UserId); - - instance.LoadPlayer(gameplayState.Score); syncManager.AddSlave(instance.GameplayClock); leaderboard.AddClock(instance.UserId, instance.GameplayClock); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 8c54fc9ec2..31d2d458df 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.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; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; @@ -23,6 +25,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private BeatmapManager beatmapManager { get; set; } + private readonly Container content; + private readonly LoadingLayer loadingLayer; private OsuScreenStack stack; public PlayerInstance(int userId, SpectatorCatchUpSlaveClock gameplayClock) @@ -32,23 +36,29 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate RelativeSizeAxes = Axes.Both; Masking = true; + + InternalChildren = new Drawable[] + { + content = new DrawSizePreservingFillContainer { RelativeSizeAxes = Axes.Both }, + loadingLayer = new LoadingLayer(true) { State = { Value = Visibility.Visible } } + }; } - public void LoadPlayer(Score score) + public void LoadScore(Score score) { + if (Score != null) + throw new InvalidOperationException($"Cannot load a new score on a {nameof(PlayerInstance)} with an existing score."); + Score = score; - InternalChild = new GameplayIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap, bypassCache: true), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) + content.Child = new GameplayIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap, bypassCache: true), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, - Child = new DrawSizePreservingFillContainer - { - RelativeSizeAxes = Axes.Both, - Child = stack = new OsuScreenStack() - } + Child = stack = new OsuScreenStack() }; stack.Push(new MultiplayerSpectatorPlayerLoader(Score, () => new MultiplayerSpectatorPlayer(Score, GameplayClock))); + loadingLayer.Hide(); } // Player interferes with global input, so disable input for now. From b15838b22027746fd9174e5fb72e10ade6a68cd2 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Fri, 16 Apr 2021 00:59:10 -0400 Subject: [PATCH 038/708] 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 039/708] 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 f98ffbb1b365294b424ee28938072ba59b9cf1e2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:15:42 +0900 Subject: [PATCH 040/708] Remove ClockToProcess, always process underlying clock --- osu.Game/Screens/Play/GameplayClock.cs | 18 +++++++++--------- .../Screens/Play/GameplayClockContainer.cs | 4 +--- 2 files changed, 10 insertions(+), 12 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); } } From 7d5d7088cd4dd1be6a0c1ec5f7a8113a2ab68aa4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:51:07 +0900 Subject: [PATCH 041/708] Remove now unnecessary override --- .../Spectate/MultiplayerSpectatorPlayer.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index 19cc9f18ad..d4ff65cd01 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Scoring; @@ -33,21 +32,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public class SubGameplayClockContainer : GameplayClockContainer { - public new DecoupleableInterpolatingFramedClock AdjustableClock => base.AdjustableClock; - public SubGameplayClockContainer(IClock sourceClock) : base(sourceClock) { } - protected override void OnIsPausedChanged(ValueChangedEvent isPaused) - { - if (isPaused.NewValue) - AdjustableClock.Stop(); - else - AdjustableClock.Start(); - } - protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) => new GameplayClock(source); } } From 4c5d4752b13807081be44f17a736822f6f57e3dc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 22:47:52 +0900 Subject: [PATCH 042/708] Rename classes to reduce redundant naming --- ...rTest.cs => TestCaseCatchUpSyncManager.cs} | 48 +++++++++---------- .../Spectate/MultiplayerSpectator.cs | 6 +-- .../Spectate/MultiplayerSpectatorPlayer.cs | 4 +- .../Multiplayer/Spectate/PlayerInstance.cs | 4 +- ...chUpSlaveClock.cs => CatchUpSlaveClock.cs} | 6 +-- ...UpSyncManager.cs => CatchUpSyncManager.cs} | 12 ++--- ...ISpectatorSlaveClock.cs => ISlaveClock.cs} | 4 +- ...pectatorSyncManager.cs => ISyncManager.cs} | 16 +++---- 8 files changed, 50 insertions(+), 50 deletions(-) rename osu.Game.Tests/OnlinePlay/{MultiplayerCatchUpSyncManagerTest.cs => TestCaseCatchUpSyncManager.cs} (73%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/{SpectatorCatchUpSlaveClock.cs => CatchUpSlaveClock.cs} (90%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/{SpectatorCatchUpSyncManager.cs => CatchUpSyncManager.cs} (90%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/{ISpectatorSlaveClock.cs => ISlaveClock.cs} (83%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/{ISpectatorSyncManager.cs => ISyncManager.cs} (50%) diff --git a/osu.Game.Tests/OnlinePlay/MultiplayerCatchUpSyncManagerTest.cs b/osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs similarity index 73% rename from osu.Game.Tests/OnlinePlay/MultiplayerCatchUpSyncManagerTest.cs rename to osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs index 6aa1219d53..a8c0a763e9 100644 --- a/osu.Game.Tests/OnlinePlay/MultiplayerCatchUpSyncManagerTest.cs +++ b/osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs @@ -12,20 +12,20 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.OnlinePlay { [HeadlessTest] - public class MultiplayerCatchUpSyncManagerTest : OsuTestScene + public class TestCaseCatchUpSyncManager : OsuTestScene { private TestManualClock master; - private SpectatorCatchUpSyncManager syncManager; + private CatchUpSyncManager syncManager; - private TestSpectatorSlaveClock slave1; - private TestSpectatorSlaveClock slave2; + private TestSlaveClock slave1; + private TestSlaveClock slave2; [SetUp] public void Setup() { - syncManager = new SpectatorCatchUpSyncManager(master = new TestManualClock()); - syncManager.AddSlave(slave1 = new TestSpectatorSlaveClock(1)); - syncManager.AddSlave(slave2 = new TestSpectatorSlaveClock(2)); + syncManager = new CatchUpSyncManager(master = new TestManualClock()); + syncManager.AddSlave(slave1 = new TestSlaveClock(1)); + syncManager.AddSlave(slave2 = new TestSlaveClock(2)); Schedule(() => Child = syncManager); } @@ -47,7 +47,7 @@ namespace osu.Game.Tests.OnlinePlay [Test] public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime() { - AddWaitStep($"wait {SpectatorCatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(SpectatorCatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); assertMasterState(false); } @@ -55,7 +55,7 @@ namespace osu.Game.Tests.OnlinePlay public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime() { setWaiting(() => slave1, false); - AddWaitStep($"wait {SpectatorCatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(SpectatorCatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); + AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); assertMasterState(true); } @@ -64,7 +64,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(SpectatorCatchUpSyncManager.SYNC_TARGET + 1); + setMasterTime(CatchUpSyncManager.SYNC_TARGET + 1); assertCatchingUp(() => slave1, false); } @@ -73,7 +73,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(SpectatorCatchUpSyncManager.MAX_SYNC_OFFSET + 1); + setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1); assertCatchingUp(() => slave1, true); assertCatchingUp(() => slave2, true); } @@ -83,8 +83,8 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(SpectatorCatchUpSyncManager.MAX_SYNC_OFFSET + 1); - setSlaveTime(() => slave1, SpectatorCatchUpSyncManager.SYNC_TARGET + 1); + setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1); + setSlaveTime(() => slave1, CatchUpSyncManager.SYNC_TARGET + 1); assertCatchingUp(() => slave1, true); } @@ -93,8 +93,8 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setMasterTime(SpectatorCatchUpSyncManager.MAX_SYNC_OFFSET + 2); - setSlaveTime(() => slave1, SpectatorCatchUpSyncManager.SYNC_TARGET); + setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 2); + setSlaveTime(() => slave1, CatchUpSyncManager.SYNC_TARGET); assertCatchingUp(() => slave1, false); assertCatchingUp(() => slave2, true); } @@ -104,7 +104,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setSlaveTime(() => slave1, -SpectatorCatchUpSyncManager.SYNC_TARGET); + setSlaveTime(() => slave1, -CatchUpSyncManager.SYNC_TARGET); assertCatchingUp(() => slave1, false); assertSlaveState(() => slave1, true); } @@ -114,7 +114,7 @@ namespace osu.Game.Tests.OnlinePlay { setAllWaiting(false); - setSlaveTime(() => slave1, -SpectatorCatchUpSyncManager.SYNC_TARGET - 1); + setSlaveTime(() => slave1, -CatchUpSyncManager.SYNC_TARGET - 1); // This is a silent catchup, where IsCatchingUp = false but IsRunning = false also. assertCatchingUp(() => slave1, false); @@ -135,7 +135,7 @@ namespace osu.Game.Tests.OnlinePlay assertSlaveState(() => slave1, false); } - private void setWaiting(Func slave, bool waiting) + private void setWaiting(Func slave, bool waiting) => AddStep($"set slave {slave().Id} waiting = {waiting}", () => slave().WaitingOnFrames.Value = waiting); private void setAllWaiting(bool waiting) => AddStep($"set all slaves waiting = {waiting}", () => @@ -150,28 +150,28 @@ namespace osu.Game.Tests.OnlinePlay /// /// slave.Time = master.Time - offsetFromMaster /// - private void setSlaveTime(Func slave, double offsetFromMaster) + private void setSlaveTime(Func slave, double offsetFromMaster) => AddStep($"set slave {slave().Id} = master - {offsetFromMaster}", () => slave().Seek(master.CurrentTime - offsetFromMaster)); private void assertMasterState(bool running) => AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running); - private void assertCatchingUp(Func slave, bool catchingUp) => + private void assertCatchingUp(Func slave, bool catchingUp) => AddAssert($"slave {slave().Id} {(catchingUp ? "is" : "is not")} catching up", () => slave().IsCatchingUp == catchingUp); - private void assertSlaveState(Func slave, bool running) + private void assertSlaveState(Func slave, bool running) => AddAssert($"slave {slave().Id} {(running ? "is" : "is not")} running", () => slave().IsRunning == running); - private class TestSpectatorSlaveClock : TestManualClock, ISpectatorSlaveClock + private class TestSlaveClock : TestManualClock, ISlaveClock { public readonly Bindable WaitingOnFrames = new Bindable(true); - IBindable ISpectatorSlaveClock.WaitingOnFrames => WaitingOnFrames; + IBindable ISlaveClock.WaitingOnFrames => WaitingOnFrames; public bool IsCatchingUp { get; set; } public readonly int Id; - public TestSpectatorSlaveClock(int id) + public TestSlaveClock(int id) { Id = id; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index e1af95b870..23cc787e20 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly PlayerInstance[] instances; private MasterGameplayClockContainer masterClockContainer; - private ISpectatorSyncManager syncManager; + private ISyncManager syncManager; private PlayerGrid grid; private MultiplayerSpectatorLeaderboard leaderboard; @@ -66,12 +66,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate InternalChildren = new[] { - (Drawable)(syncManager = new SpectatorCatchUpSyncManager(masterClockContainer)), + (Drawable)(syncManager = new CatchUpSyncManager(masterClockContainer)), masterClockContainer }; for (int i = 0; i < UserIds.Length; i++) - grid.Add(instances[i] = new PlayerInstance(UserIds[i], new SpectatorCatchUpSlaveClock(masterClockContainer.GameplayClock))); + grid.Add(instances[i] = new PlayerInstance(UserIds[i], new CatchUpSlaveClock(masterClockContainer.GameplayClock))); // Todo: This is not quite correct - it should be per-user to adjust for other mod combinations. var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index d4ff65cd01..55f483586c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -12,9 +12,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiplayerSpectatorPlayer : SpectatorPlayer { - private readonly ISpectatorSlaveClock gameplayClock; + private readonly ISlaveClock gameplayClock; - public MultiplayerSpectatorPlayer(Score score, ISpectatorSlaveClock gameplayClock) + public MultiplayerSpectatorPlayer(Score score, ISlaveClock gameplayClock) : base(score) { this.gameplayClock = gameplayClock; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 31d2d458df..407732ee2e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public bool PlayerLoaded => stack?.CurrentScreen is Player; public readonly int UserId; - public readonly SpectatorCatchUpSlaveClock GameplayClock; + public readonly CatchUpSlaveClock GameplayClock; public Score Score { get; private set; } @@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly LoadingLayer loadingLayer; private OsuScreenStack stack; - public PlayerInstance(int userId, SpectatorCatchUpSlaveClock gameplayClock) + public PlayerInstance(int userId, CatchUpSlaveClock gameplayClock) { UserId = userId; GameplayClock = gameplayClock; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs similarity index 90% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSlaveClock.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs index 4b529021de..f128ea619b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs @@ -8,9 +8,9 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { /// - /// A which catches up using rate adjustment. + /// A which catches up using rate adjustment. /// - public class SpectatorCatchUpSlaveClock : ISpectatorSlaveClock + public class CatchUpSlaveClock : ISlaveClock { /// /// The catch up rate. @@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync private readonly IFrameBasedClock masterClock; - public SpectatorCatchUpSlaveClock(IFrameBasedClock masterClock) + public CatchUpSlaveClock(IFrameBasedClock masterClock) { this.masterClock = masterClock; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs similarity index 90% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSyncManager.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs index 46e86177ca..68bfef6500 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/SpectatorCatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs @@ -10,9 +10,9 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { /// - /// A which synchronises de-synced slave clocks through catchup. + /// A which synchronises de-synced slave clocks through catchup. /// - public class SpectatorCatchUpSyncManager : Component, ISpectatorSyncManager + public class CatchUpSyncManager : Component, ISyncManager { /// /// The offset from the master clock to which slaves should be synchronised to. @@ -37,19 +37,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync /// /// The slave clocks. /// - private readonly List slaves = new List(); + private readonly List slaves = new List(); private bool hasStarted; private double? firstStartAttemptTime; - public SpectatorCatchUpSyncManager(IAdjustableClock master) + public CatchUpSyncManager(IAdjustableClock master) { Master = master; } - public void AddSlave(ISpectatorSlaveClock clock) => slaves.Add(clock); + public void AddSlave(ISlaveClock clock) => slaves.Add(clock); - public void RemoveSlave(ISpectatorSlaveClock clock) => slaves.Remove(clock); + public void RemoveSlave(ISlaveClock clock) => slaves.Remove(clock); protected override void Update() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs similarity index 83% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSlaveClock.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs index d8733d4322..f45cb1fde6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs @@ -7,9 +7,9 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { /// - /// A clock which is used by s and managed by an . + /// A clock which is used by s and managed by an . /// - public interface ISpectatorSlaveClock : IFrameBasedClock, IAdjustableClock + public interface ISlaveClock : IFrameBasedClock, IAdjustableClock { /// /// Whether this clock is waiting on frames to continue playback. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISyncManager.cs similarity index 50% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISyncManager.cs index 5edbb3bc2c..d63ac7e1ca 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISyncManager.cs @@ -6,9 +6,9 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { /// - /// Manages the synchronisation between one or more s in relation to a master clock. + /// Manages the synchronisation between one or more s in relation to a master clock. /// - public interface ISpectatorSyncManager + public interface ISyncManager { /// /// The master clock which slaves should synchronise to. @@ -16,15 +16,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync IAdjustableClock Master { get; } /// - /// Adds an to manage. + /// Adds an to manage. /// - /// The to add. - void AddSlave(ISpectatorSlaveClock clock); + /// The to add. + void AddSlave(ISlaveClock clock); /// - /// Removes an , stopping it from being managed by this . + /// Removes an , stopping it from being managed by this . /// - /// The to remove. - void RemoveSlave(ISpectatorSlaveClock clock); + /// The to remove. + void RemoveSlave(ISlaveClock clock); } } From 72ebcb157f585869b5b41593d0d262561ffa7369 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 22:57:27 +0900 Subject: [PATCH 043/708] Dispose track on dispose --- .../Spectate/GameplayIsolationContainer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs index 3ccb67d342..7b6a544084 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -22,13 +23,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Cached] private readonly Bindable> mods = new Bindable>(); + private readonly Track track; + public GameplayIsolationContainer(WorkingBeatmap beatmap, RulesetInfo ruleset, IReadOnlyList mods) { this.beatmap.Value = beatmap; this.ruleset.Value = ruleset; this.mods.Value = mods; - beatmap.LoadTrack(); + track = beatmap.LoadTrack(); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -39,5 +42,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate dependencies.CacheAs(mods.BeginLease(false)); return dependencies; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + track?.Dispose(); + } } } From 724fe3d37847ee70044e90c21c219ff79da03cc4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 22:57:34 +0900 Subject: [PATCH 044/708] Remove unnecessary method --- .../OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs index b2d114d9cc..830378f129 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -75,19 +75,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate cellContainer.Add(cell); } - public void Remove(Drawable content) - { - var cell = cellContainer.FirstOrDefault(c => c.Content == content); - if (cell == null) - return; - - if (cell.IsMaximised) - toggleMaximisationState(cell); - - cellContainer.Remove(cell); - facadeContainer.Remove(facadeContainer[cell.FacadeIndex]); - } - /// /// The content added to this grid. /// From d5b26b0ab506a62ececcf94781df307023ca0362 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 23:01:34 +0900 Subject: [PATCH 045/708] Fix incorrect test spectator client implementation --- .../Visual/Multiplayer/TestSceneMultiplayerSpectator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs index 4026258830..e395ebd29d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs @@ -321,7 +321,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public override void WatchUser(int userId) { - if (userSentStateDictionary.TryGetValue(userId, out var sent) && sent) + if (!PlayingUsers.Contains(userId) && userSentStateDictionary.TryGetValue(userId, out var sent) && sent) { // usually the server would do this. sendState(userId, userBeatmapDictionary[userId]); From 512cec3458f53c29b135d65d49a4af8b86be8fe2 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 00:08:16 +0800 Subject: [PATCH 046/708] Use unicode for playlists --- .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 23c713a2c1..4fccf45fda 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -13,11 +13,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; @@ -105,10 +107,18 @@ namespace osu.Game.Screens.OnlinePlay private void refresh() { + var beatmapMetadata = Item.Beatmap.Value.Metadata ?? Item.Beatmap.Value.BeatmapSet?.Metadata; difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - beatmapText.AddLink(Item.Beatmap.ToString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); + var text = new List + { + new OsuSpriteText { Text = new RomanisableString(beatmapMetadata.ArtistUnicode, beatmapMetadata.Artist) }, + new OsuSpriteText { Text = " - " }, + new OsuSpriteText { Text = new RomanisableString(beatmapMetadata.TitleUnicode, beatmapMetadata.Title) }, + new OsuSpriteText { Text = $" ({beatmapMetadata.Author}) [{Item.Beatmap.Value.Version}]" } + }; + beatmapText.AddLink(text, LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); From 81be562379d5bb60023be3f20ecb1f8990993ef9 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 12:28:31 -0400 Subject: [PATCH 047/708] 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 048/708] 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 049/708] 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 e40cb6797df3896d6179dc0f85edef8168c5ee09 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 15:27:22 -0400 Subject: [PATCH 050/708] 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 051/708] 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 97bacbdc760942ccb7052441b249f6a1c87ce2bd Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 17:55:46 -0400 Subject: [PATCH 052/708] 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 c77f838fb051a4edaf2de036438045d489c10db2 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 21:49:07 -0400 Subject: [PATCH 053/708] 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 054/708] 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 cfaaf2e83e3112ea16e786c9a7487a58e794cd17 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 09:52:25 +0800 Subject: [PATCH 055/708] Add ToRomanisableString() --- osu.Game/Beatmaps/BeatmapInfo.cs | 12 ++++++++---- osu.Game/Beatmaps/BeatmapMetadata.cs | 7 +++++++ .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 11 ++--------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index bf7906bd5c..c5c5bd208c 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.IO.Serialization; @@ -127,6 +128,8 @@ namespace osu.Game.Beatmaps // Metadata public string Version { get; set; } + public string VersionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; + [JsonProperty("difficulty_rating")] public double StarDifficulty { get; set; } @@ -143,11 +146,12 @@ namespace osu.Game.Beatmaps Version }.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); - public override string ToString() - { - string version = string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; + public override string ToString() => $"{Metadata ?? BeatmapSet?.Metadata} {VersionString}".Trim(); - return $"{Metadata ?? BeatmapSet?.Metadata} {version}".Trim(); + public RomanisableString ToRomanisableString() + { + var metadata = (Metadata ?? BeatmapSet?.Metadata).ToRomanisableString() ?? new RomanisableString(null, null); + return new RomanisableString($"{metadata.GetPreferred(true)} {VersionString}".Trim(), $"{metadata.GetPreferred(false)} {VersionString}".Trim()); } public bool Equals(BeatmapInfo other) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 858da8e602..02d1349794 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Users; @@ -71,6 +72,12 @@ namespace osu.Game.Beatmaps return $"{Artist} - {Title} {author}".Trim(); } + public RomanisableString ToRomanisableString() + { + string author = Author == null ? string.Empty : $"({Author})"; + return new RomanisableString($"{ArtistUnicode} - {TitleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim()); + } + [JsonIgnore] public string[] SearchableTerms => new[] { diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 4fccf45fda..69173bd53b 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -107,18 +107,11 @@ namespace osu.Game.Screens.OnlinePlay private void refresh() { - var beatmapMetadata = Item.Beatmap.Value.Metadata ?? Item.Beatmap.Value.BeatmapSet?.Metadata; difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - var text = new List - { - new OsuSpriteText { Text = new RomanisableString(beatmapMetadata.ArtistUnicode, beatmapMetadata.Artist) }, - new OsuSpriteText { Text = " - " }, - new OsuSpriteText { Text = new RomanisableString(beatmapMetadata.TitleUnicode, beatmapMetadata.Title) }, - new OsuSpriteText { Text = $" ({beatmapMetadata.Author}) [{Item.Beatmap.Value.Version}]" } - }; - beatmapText.AddLink(text, LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); + beatmapText.AddLink(new List{ new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() } } + , LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); From 1339c126a49d9d52def6ef031959812f3ea48794 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 09:54:50 +0800 Subject: [PATCH 056/708] Remove unused using --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 69173bd53b..ca8e13d10a 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; From 7b1d40db7de4fc6544ebc1f4070d600a024df488 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 17 Apr 2021 22:13:28 -0400 Subject: [PATCH 057/708] 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 646403b826da7d5b3e2600ea93ac2ab2c01e9346 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 10:00:17 +0800 Subject: [PATCH 058/708] Fix CI errors --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index c5c5bd208c..d5a1e2491b 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -150,7 +150,7 @@ namespace osu.Game.Beatmaps public RomanisableString ToRomanisableString() { - var metadata = (Metadata ?? BeatmapSet?.Metadata).ToRomanisableString() ?? new RomanisableString(null, null); + var metadata = (Metadata ?? BeatmapSet?.Metadata)?.ToRomanisableString() ?? new RomanisableString(null, null); return new RomanisableString($"{metadata.GetPreferred(true)} {VersionString}".Trim(), $"{metadata.GetPreferred(false)} {VersionString}".Trim()); } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index ca8e13d10a..fe5876e732 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -109,8 +109,8 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - beatmapText.AddLink(new List{ new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() } } - , LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); + beatmapText.AddLink(new List { new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() } }, + LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); From a73bae7a66889f22f8a321745baf25eafafd3efe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 18 Apr 2021 06:35:43 +0300 Subject: [PATCH 059/708] 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 060/708] 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 061/708] 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 38a7c590c4d07ac17c9746c8feac2aa4a59c63af Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 20:57:25 +0800 Subject: [PATCH 062/708] Make versionString private --- osu.Game/Beatmaps/BeatmapInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index d5a1e2491b..36cb97e8d7 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -128,7 +128,7 @@ namespace osu.Game.Beatmaps // Metadata public string Version { get; set; } - public string VersionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; + private string versionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; [JsonProperty("difficulty_rating")] public double StarDifficulty { get; set; } @@ -146,12 +146,12 @@ namespace osu.Game.Beatmaps Version }.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); - public override string ToString() => $"{Metadata ?? BeatmapSet?.Metadata} {VersionString}".Trim(); + public override string ToString() => $"{Metadata ?? BeatmapSet?.Metadata} {versionString}".Trim(); public RomanisableString ToRomanisableString() { var metadata = (Metadata ?? BeatmapSet?.Metadata)?.ToRomanisableString() ?? new RomanisableString(null, null); - return new RomanisableString($"{metadata.GetPreferred(true)} {VersionString}".Trim(), $"{metadata.GetPreferred(false)} {VersionString}".Trim()); + return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim()); } public bool Equals(BeatmapInfo other) From 488001d5701b51db7405e5ebd7c3ab4174e9a699 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 18 Apr 2021 20:58:08 +0800 Subject: [PATCH 063/708] Support SpriteText for LinkFlowContainer --- .../Graphics/Containers/LinkFlowContainer.cs | 23 +++++++++++++++++++ osu.Game/Online/Chat/DrawableLinkCompiler.cs | 5 ++++ .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 914c8ff78d..32efcce80c 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -67,6 +67,13 @@ namespace osu.Game.Graphics.Containers createLink(text, new LinkDetails(action, linkArgument), tooltipText); } + public void AddLink(SpriteText text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) + { + AddArbitraryDrawable(text); + + createLink(text, new LinkDetails(action, linkArgument), tooltipText); + } + public void AddUserLink(User user, Action creationParameters = null) => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile"); @@ -86,6 +93,22 @@ namespace osu.Game.Graphics.Containers }); } + private void createLink(Drawable drawable, LinkDetails link, string tooltipText, Action action = null) + { + AddInternal(new DrawableLinkCompiler(drawable) + { + RelativeSizeAxes = Axes.Both, + TooltipText = tooltipText, + Action = () => + { + if (action != null) + action(); + else + game?.HandleLink(link); + }, + }); + } + // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation. // Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow. diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index d27a3fbffe..7675eab4c0 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -31,6 +31,11 @@ namespace osu.Game.Online.Chat Parts = parts.ToList(); } + public DrawableLinkCompiler(Drawable part) + { + Parts = new List { part }; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index fe5876e732..a92ca610f3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - beatmapText.AddLink(new List { new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() } }, + beatmapText.AddLink(new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() }, LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); From 98460c8febc37c17acb949a864f528c32ef637cb Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sun, 18 Apr 2021 00:45:24 -0400 Subject: [PATCH 064/708] 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 065/708] 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 066/708] 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 0b36dd9bce6e4f958b51e91d52c7fd650abf2673 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 19 Apr 2021 01:23:21 -0400 Subject: [PATCH 067/708] 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 068/708] 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 ef3801b5dd619d1fb0c5edf32c4d683ad26bd4ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 15:29:26 +0900 Subject: [PATCH 069/708] Add helper method supporting RomanisableString --- .../Graphics/Containers/LinkFlowContainer.cs | 34 ++++++------------- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 4 +-- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 32efcce80c..3a2f9d5a78 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -7,7 +7,10 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Graphics.Sprites; using osu.Game.Users; namespace osu.Game.Graphics.Containers @@ -59,6 +62,14 @@ namespace osu.Game.Graphics.Containers public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText); + public void AddLink(RomanisableString text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) + { + var spriteText = new OsuSpriteText { Text = text }; + + AddText(spriteText, creationParameters); + createLink(spriteText.Yield(), new LinkDetails(action, argument), tooltipText); + } + public void AddLink(IEnumerable text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) { foreach (var t in text) @@ -67,13 +78,6 @@ namespace osu.Game.Graphics.Containers createLink(text, new LinkDetails(action, linkArgument), tooltipText); } - public void AddLink(SpriteText text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) - { - AddArbitraryDrawable(text); - - createLink(text, new LinkDetails(action, linkArgument), tooltipText); - } - public void AddUserLink(User user, Action creationParameters = null) => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile"); @@ -93,22 +97,6 @@ namespace osu.Game.Graphics.Containers }); } - private void createLink(Drawable drawable, LinkDetails link, string tooltipText, Action action = null) - { - AddInternal(new DrawableLinkCompiler(drawable) - { - RelativeSizeAxes = Axes.Both, - TooltipText = tooltipText, - Action = () => - { - if (action != null) - action(); - else - game?.HandleLink(link); - }, - }); - } - // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation. // Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow. diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index a92ca610f3..38a9ace619 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -18,7 +18,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; @@ -109,8 +108,7 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - beatmapText.AddLink(new OsuSpriteText { Text = Item.Beatmap.Value.ToRomanisableString() }, - LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); + beatmapText.AddLink(Item.Beatmap.Value.ToRomanisableString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); From 25c7dc9ef0f7716a08878733b9cba18063cfef48 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 15:30:11 +0900 Subject: [PATCH 070/708] Revert unnecessary change --- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index 7675eab4c0..d27a3fbffe 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -31,11 +31,6 @@ namespace osu.Game.Online.Chat Parts = parts.ToList(); } - public DrawableLinkCompiler(Drawable part) - { - Parts = new List { part }; - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { From 9f24cdb355bff38f788c6b2cb6e1f8bfce0b7e5b Mon Sep 17 00:00:00 2001 From: Denrage Date: Mon, 19 Apr 2021 16:53:44 +0200 Subject: [PATCH 071/708] Added Author Link in BeatmapInfoWedge --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 29 +++++++++------------ 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 4069dc82ed..14e5831ef9 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -29,6 +29,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Ranking.Expanded; +using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Select { @@ -307,7 +308,7 @@ namespace osu.Game.Screens.Select Margin = new MarginPadding { Top = 10 }, Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Children = getMapper(metadata) + Children = new[] { getMapper(metadata) }, }, infoLabelContainer = new FillFlowContainer { @@ -430,24 +431,20 @@ namespace osu.Game.Screens.Select ForceRedraw(); } - private OsuSpriteText[] getMapper(BeatmapMetadata metadata) + private LinkFlowContainer getMapper(BeatmapMetadata metadata) { - if (string.IsNullOrEmpty(metadata.Author?.Username)) - return Array.Empty(); + if (metadata.Author == null) + return new LinkFlowContainer(); - return new[] + return new LinkFlowContainer(s => { - new OsuSpriteText - { - Text = "mapped by ", - Font = OsuFont.GetFont(size: 15), - }, - new OsuSpriteText - { - Text = metadata.Author.Username, - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15), - } - }; + s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.AddText("mapped by "); + d.AddUserLink(metadata.Author); + }); } protected override void Dispose(bool isDisposing) From eb977312ed71bee57676714afb2a81c0522bdd9d Mon Sep 17 00:00:00 2001 From: Denrage Date: Mon, 19 Apr 2021 16:54:29 +0200 Subject: [PATCH 072/708] Added Author link in BeatmapSet Overlay --- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 31c1439c8f..1ffcf9722a 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -13,6 +13,8 @@ using osuTK.Graphics; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Users; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.BeatmapSet { @@ -50,7 +52,7 @@ namespace osu.Game.Overlays.BeatmapSet fields.Children = new Drawable[] { - new Field("mapped by", BeatmapSet.Metadata.Author.Username, OsuFont.GetFont(weight: FontWeight.Regular, italics: true)), + new Field("mapped by", BeatmapSet.Metadata.Author, OsuFont.GetFont(weight: FontWeight.Regular, italics: true)), new Field("submitted", online.Submitted, OsuFont.GetFont(weight: FontWeight.Bold)) { Margin = new MarginPadding { Top = 5 }, @@ -146,6 +148,25 @@ namespace osu.Game.Overlays.BeatmapSet } }; } + + public Field(string first, User second, FontUsage secondFont) + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + + Children = new[] + { + new LinkFlowContainer(s => + { + s.Font = OsuFont.GetFont(size: 11); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.AddText($"{first} "); + d.AddUserLink(second, s => s.Font = secondFont.With(size: 11)); + }), + }; + } } } } From 6bf6084ae9c3129d8a5672946e33205e8eabfcd3 Mon Sep 17 00:00:00 2001 From: Denrage Date: Mon, 19 Apr 2021 19:24:46 +0200 Subject: [PATCH 073/708] fixed according to suggestions --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 14e5831ef9..deb77c27de 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -308,7 +308,7 @@ namespace osu.Game.Screens.Select Margin = new MarginPadding { Top = 10 }, Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Children = new[] { getMapper(metadata) }, + Child = getMapper(metadata), }, infoLabelContainer = new FillFlowContainer { @@ -431,10 +431,10 @@ namespace osu.Game.Screens.Select ForceRedraw(); } - private LinkFlowContainer getMapper(BeatmapMetadata metadata) + private Drawable getMapper(BeatmapMetadata metadata) { if (metadata.Author == null) - return new LinkFlowContainer(); + return Empty(); return new LinkFlowContainer(s => { From c73bbf0aa7566c1bf33784caacb91fc786a7a2cc Mon Sep 17 00:00:00 2001 From: Denrage Date: Mon, 19 Apr 2021 19:24:58 +0200 Subject: [PATCH 074/708] Fixed Tests --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 7ea6373763..16afc5ace7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("check version", () => infoWedge.Info.VersionLabel.Current.Value == $"{ruleset.ShortName}Version"); AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title"); AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); - AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType().Any(s => s.Current.Value == $"{ruleset.ShortName}Author")); + AddAssert("check author", () => infoWedge.Info.MapperContainer.ChildrenOfType().Any(s => s.Current.Value == $"{ruleset.ShortName}Author")); } private void testInfoLabels(int expectedCount) @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Current.Value)); 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 empty author", () => !infoWedge.Info.MapperContainer.ChildrenOfType().Any()); AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType().Any()); } From 2a7ef1f80f037cbf393acbf23f1bd06ee4cbc4d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Apr 2021 19:27:35 +0200 Subject: [PATCH 075/708] Use more general type --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 3a2f9d5a78..054febeec3 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -62,7 +62,7 @@ namespace osu.Game.Graphics.Containers public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText); - public void AddLink(RomanisableString text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) + public void AddLink(LocalisableString text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) { var spriteText = new OsuSpriteText { Text = text }; From 505a117862cce9818970a71833736c7a294b84c2 Mon Sep 17 00:00:00 2001 From: Denrage Date: Mon, 19 Apr 2021 23:41:51 +0200 Subject: [PATCH 076/708] 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 077/708] 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 4e6cd8082ea784ee4223f2f36e7c05f1253aeac5 Mon Sep 17 00:00:00 2001 From: Denrage Date: Tue, 20 Apr 2021 12:00:04 +0200 Subject: [PATCH 078/708] 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 079/708] 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 f799a6e733e0d2075f7feafd9333cfaf5c604fad Mon Sep 17 00:00:00 2001 From: Denrage Date: Tue, 20 Apr 2021 14:18:41 +0200 Subject: [PATCH 080/708] 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 df29e61147fd45f0f310453c19393266d10609dd Mon Sep 17 00:00:00 2001 From: Denrage Date: Tue, 20 Apr 2021 14:22:49 +0200 Subject: [PATCH 081/708] 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 082/708] 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 083/708] 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 f32d00c0d99ecccdbf1452c210f0a75769932298 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 17:13:01 +0900 Subject: [PATCH 084/708] Fix post-merge errors --- .../OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs | 2 +- osu.Game/Screens/Play/GameplayClockContainer.cs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 23cc787e20..a5bd449e7e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate base.LoadComplete(); masterClockContainer.Stop(); - masterClockContainer.Restart(); + masterClockContainer.Reset(); } protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index dac3dad5d2..4109fd32bc 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -154,11 +154,7 @@ namespace osu.Game.Screens.Play return true; } - void IAdjustableClock.Reset() - { - Restart(); - Stop(); - } + void IAdjustableClock.Reset() => Reset(); public void ResetSpeedAdjustments() { From de04caeace220426ed615efa6d3cabe4f76c88af Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 21 Apr 2021 13:53:08 +0200 Subject: [PATCH 085/708] 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 086/708] 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 087/708] 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 088/708] 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 089/708] 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 2bea6256137a82375ac2e0f05a39f95b4f286503 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 23:21:03 +0900 Subject: [PATCH 090/708] Fix initial playback states not being correct --- .../TestSceneMultiplayerSpectator.cs | 11 ++----- .../Spectate/MultiplayerSpectator.cs | 14 +++------ .../Spectate/MultiplayerSpectatorPlayer.cs | 31 ++++++++++++++++--- .../Spectate/Sync/CatchUpSlaveClock.cs | 2 +- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs index e395ebd29d..9ec7bfb60b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs @@ -256,17 +256,10 @@ namespace osu.Game.Tests.Visual.Multiplayer } private void checkPaused(int userId, bool state) => - AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().IsPaused.Value == state); + AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); private void checkPausedInstant(int userId, bool state) => - AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().IsPaused.Value == state); - - /// - /// Returns time(user1) - time(user2). - /// - private double getGameplayOffset(int user1, int user2) => getGameplayTime(user1) - getGameplayTime(user2); - - private double getGameplayTime(int userId) => getPlayer(userId).ChildrenOfType().Single().GameplayClock.CurrentTime; + AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index a5bd449e7e..2db470da67 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -39,10 +39,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void load() { Container leaderboardContainer; + masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0); - masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0) + InternalChildren = new[] { - Child = new GridContainer + (Drawable)(syncManager = new CatchUpSyncManager(masterClockContainer)), + masterClockContainer.WithChild(new GridContainer { RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] @@ -61,13 +63,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate grid = new PlayerGrid { RelativeSizeAxes = Axes.Both } } } - } - }; - - InternalChildren = new[] - { - (Drawable)(syncManager = new CatchUpSyncManager(masterClockContainer)), - masterClockContainer + }) }; for (int i = 0; i < UserIds.Length; i++) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index 55f483586c..5af0d19a4d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.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.Timing; using osu.Game.Beatmaps; using osu.Game.Scoring; @@ -12,22 +13,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiplayerSpectatorPlayer : SpectatorPlayer { - private readonly ISlaveClock gameplayClock; + private readonly Bindable waitingOnFrames = new Bindable(true); + private readonly Score score; + private readonly ISlaveClock slaveClock; - public MultiplayerSpectatorPlayer(Score score, ISlaveClock gameplayClock) + public MultiplayerSpectatorPlayer(Score score, ISlaveClock slaveClock) : base(score) { - this.gameplayClock = gameplayClock; + this.score = score; + this.slaveClock = slaveClock; } [BackgroundDependencyLoader] private void load() { - gameplayClock.WaitingOnFrames.BindTo(DrawableRuleset.FrameStableClock.WaitingOnFrames); + slaveClock.WaitingOnFrames.BindTo(waitingOnFrames); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + waitingOnFrames.Value = DrawableRuleset.FrameStableClock.WaitingOnFrames.Value || score.Replay.Frames.Count == 0; } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - => new SubGameplayClockContainer(gameplayClock); + => new SubGameplayClockContainer(slaveClock); } public class SubGameplayClockContainer : GameplayClockContainer @@ -37,6 +47,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { } + protected override void Update() + { + // The slave clock's running state is controlled by the sync manager, but the local pausing state needs to be updated to stop gameplay. + if (SourceClock.IsRunning) + Start(); + else + Stop(); + + base.Update(); + } + protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) => new GameplayClock(source); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs index f128ea619b..cefe5ff04f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync public FrameTimeInfo TimeInfo => new FrameTimeInfo { Elapsed = ElapsedFrameTime, Current = CurrentTime }; - public IBindable WaitingOnFrames { get; } = new Bindable(); + public IBindable WaitingOnFrames { get; } = new Bindable(true); public bool IsCatchingUp { get; set; } } From 1ca2152e6191e0da98abdf034f1d50c20734fe7b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 23:22:36 +0900 Subject: [PATCH 091/708] Privatise + rename to SlaveGameplayClockContainer --- .../Spectate/MultiplayerSpectatorPlayer.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index 5af0d19a4d..69b38c61f7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -37,27 +37,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - => new SubGameplayClockContainer(slaveClock); - } + => new SlaveGameplayClockContainer(slaveClock); - public class SubGameplayClockContainer : GameplayClockContainer - { - public SubGameplayClockContainer(IClock sourceClock) - : base(sourceClock) + private class SlaveGameplayClockContainer : GameplayClockContainer { + public SlaveGameplayClockContainer(IClock sourceClock) + : base(sourceClock) + { + } + + protected override void Update() + { + // The slave clock's running state is controlled by the sync manager, but the local pausing state needs to be updated to stop gameplay. + if (SourceClock.IsRunning) + Start(); + else + Stop(); + + base.Update(); + } + + protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) => new GameplayClock(source); } - - protected override void Update() - { - // The slave clock's running state is controlled by the sync manager, but the local pausing state needs to be updated to stop gameplay. - if (SourceClock.IsRunning) - Start(); - else - Stop(); - - base.Update(); - } - - protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) => new GameplayClock(source); } } From 6588859c32296e9538f819a49328382f314b62bc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 22:29:18 +0900 Subject: [PATCH 092/708] Remove loggings --- .../Multiplayer/Spectate/Sync/CatchUpSyncManager.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs index 68bfef6500..1d3fcd824c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync @@ -84,16 +83,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync int readyCount = slaves.Count(s => !s.WaitingOnFrames.Value); if (readyCount == slaves.Count) - { - Logger.Log("Gameplay started (all ready)."); return hasStarted = true; - } if (readyCount > 0 && (Time.Current - firstStartAttemptTime) > MAXIMUM_START_DELAY) - { - Logger.Log($"Gameplay started (maximum delay exceeded, {readyCount}/{slaves.Count} ready)."); return hasStarted = true; - } return false; } @@ -124,19 +117,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { // Stop the slave from catching up if it's within the sync target. if (timeDelta <= SYNC_TARGET) - { slave.IsCatchingUp = false; - Logger.Log($"Slave {i} catchup finished (delta = {timeDelta})"); - } } else { // Make the slave start catching up if it's exceeded the maximum allowable sync offset. if (timeDelta > MAX_SYNC_OFFSET) - { slave.IsCatchingUp = true; - Logger.Log($"Slave {i} catchup started (too far behind, delta = {timeDelta})"); - } } } } From 64579d50ac160215d5a8f60dc5f5fa8526b27fbc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 22:59:47 +0900 Subject: [PATCH 093/708] Use only single PlayerInstance for hit sample playback --- .../TestSceneMultiplayerSpectator.cs | 46 ++++++++++++-- .../Spectate/MultiplayerSpectator.cs | 20 +++++++ .../Multiplayer/Spectate/PlayerInstance.cs | 60 +++++++++++++++++-- 3 files changed, 117 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs index 9ec7bfb60b..cd89d3bcc1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs @@ -198,10 +198,40 @@ namespace osu.Game.Tests.Visual.Multiplayer checkPausedInstant(56, false); // Player 2 should catch up to player 1 after unpausing. - AddUntilStep("player 2 not catching up", () => !getInstance(56).GameplayClock.IsCatchingUp); + waitForCatchup(56); AddWaitStep("wait a bit", 10); } + [Test] + public void TestMostInSyncUserIsAudioSource() + { + start(new[] { 55, 56 }); + loadSpectateScreen(); + + assertVolume(55, 0); + assertVolume(56, 0); + + sendFrames(55, 10); + sendFrames(56, 20); + assertVolume(55, 1); + assertVolume(56, 0); + + checkPaused(55, true); + assertVolume(55, 0); + assertVolume(56, 1); + + sendFrames(55, 100); + waitForCatchup(55); + checkPaused(56, true); + assertVolume(55, 1); + assertVolume(56, 0); + + sendFrames(56, 100); + waitForCatchup(56); + assertVolume(55, 1); + assertVolume(56, 0); + } + private void loadSpectateScreen(bool waitForPlayerLoad = true) { AddStep("load screen", () => @@ -255,11 +285,17 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private void checkPaused(int userId, bool state) => - AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); + private void checkPaused(int userId, bool state) + => AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); - private void checkPausedInstant(int userId, bool state) => - AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); + private void checkPausedInstant(int userId, bool state) + => AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); + + private void assertVolume(int userId, double volume) + => AddAssert($"{userId} volume is {volume}", () => getInstance(userId).Volume.Value == volume); + + private void waitForCatchup(int userId) + => AddUntilStep($"{userId} not catching up", () => !getInstance(userId).GameplayClock.IsCatchingUp); private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index 2db470da67..33dd57586c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -28,6 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private ISyncManager syncManager; private PlayerGrid grid; private MultiplayerSpectatorLeaderboard leaderboard; + private PlayerInstance currentAudioSource; public MultiplayerSpectator(int[] userIds) : base(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) @@ -85,6 +87,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate masterClockContainer.Reset(); } + protected override void Update() + { + base.Update(); + + if (!isCandidateAudioSource(currentAudioSource?.GameplayClock)) + { + currentAudioSource = instances.Where(i => isCandidateAudioSource(i.GameplayClock)) + .OrderBy(i => Math.Abs(i.GameplayClock.CurrentTime - syncManager.Master.CurrentTime)) + .FirstOrDefault(); + + foreach (var instance in instances) + instance.Volume.Value = instance == currentAudioSource ? 1 : 0; + } + } + + private bool isCandidateAudioSource([CanBeNull] ISlaveClock clock) + => clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames.Value; + protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 407732ee2e..80d6727599 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -13,7 +15,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public class PlayerInstance : CompositeDrawable + public class PlayerInstance : CompositeDrawable, IAdjustableAudioComponent { public bool PlayerLoaded => stack?.CurrentScreen is Player; @@ -25,8 +27,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private BeatmapManager beatmapManager { get; set; } - private readonly Container content; + private readonly Container gameplayContent; private readonly LoadingLayer loadingLayer; + private readonly AudioContainer audioContainer; private OsuScreenStack stack; public PlayerInstance(int userId, CatchUpSlaveClock gameplayClock) @@ -39,7 +42,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate InternalChildren = new Drawable[] { - content = new DrawSizePreservingFillContainer { RelativeSizeAxes = Axes.Both }, + audioContainer = new AudioContainer + { + RelativeSizeAxes = Axes.Both, + Child = gameplayContent = new DrawSizePreservingFillContainer { RelativeSizeAxes = Axes.Both }, + }, loadingLayer = new LoadingLayer(true) { State = { Value = Visibility.Visible } } }; } @@ -51,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Score = score; - content.Child = new GameplayIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap, bypassCache: true), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) + gameplayContent.Child = new GameplayIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, Child = stack = new OsuScreenStack() @@ -64,5 +71,50 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate // Player interferes with global input, so disable input for now. public override bool PropagatePositionalInputSubTree => false; public override bool PropagateNonPositionalInputSubTree => false; + + #region IAdjustableAudioComponent + + public IBindable AggregateVolume => audioContainer.AggregateVolume; + + public IBindable AggregateBalance => audioContainer.AggregateBalance; + + public IBindable AggregateFrequency => audioContainer.AggregateFrequency; + + public IBindable AggregateTempo => audioContainer.AggregateTempo; + + public void BindAdjustments(IAggregateAudioAdjustment component) + { + audioContainer.BindAdjustments(component); + } + + public void UnbindAdjustments(IAggregateAudioAdjustment component) + { + audioContainer.UnbindAdjustments(component); + } + + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + audioContainer.AddAdjustment(type, adjustBindable); + } + + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + audioContainer.RemoveAdjustment(type, adjustBindable); + } + + public void RemoveAllAdjustments(AdjustableProperty type) + { + audioContainer.RemoveAllAdjustments(type); + } + + public BindableNumber Volume => audioContainer.Volume; + + public BindableNumber Balance => audioContainer.Balance; + + public BindableNumber Frequency => audioContainer.Frequency; + + public BindableNumber Tempo => audioContainer.Tempo; + + #endregion } } From 22a3f3ad3e9f022fb85e1258aa0a2184a58fe671 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 23:04:54 +0900 Subject: [PATCH 094/708] Revert unnecessary BeatmapManager change --- osu.Game/Beatmaps/BeatmapManager.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index abb1f5a93c..5e975de77c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -281,9 +281,8 @@ namespace osu.Game.Beatmaps /// /// The beatmap to lookup. /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. May be returned if beatmapInfo requested matches - /// Whether to bypass the cache and return a new instance. /// A instance correlating to the provided . - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null, bool bypassCache = false) + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) { if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) return previous; @@ -302,14 +301,9 @@ namespace osu.Game.Beatmaps lock (workingCache) { - BeatmapManagerWorkingBeatmap working; - - if (!bypassCache) - { - working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); - if (working != null) - return working; - } + var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); + if (working != null) + return working; beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; From fd0b030cf431ecb0ab3ea45bbcac68ee0545a207 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 23:37:33 +0900 Subject: [PATCH 095/708] Refactor gameplay screen creation --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 13 +++++++----- .../Multiplayer/MultiplayerMatchSubScreen.cs | 21 ++++++++++++------- .../Playlists/PlaylistsRoomSubScreen.cs | 9 +++----- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 0d1ed5d88e..68bdd9160e 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -17,7 +17,6 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Match { @@ -148,14 +147,18 @@ namespace osu.Game.Screens.OnlinePlay.Match return base.OnExiting(next); } - protected void StartPlay(Func player) => PushTopLevelScreen(() => new PlayerLoader(player)); - - protected void PushTopLevelScreen(Func screen) + protected void StartPlay() { sampleStart?.Play(); - ParentScreen?.Push(screen()); + ParentScreen?.Push(CreateGameplayScreen()); } + /// + /// Creates the gameplay screen to be entered. + /// + /// The screen to enter. + protected abstract Screen CreateGameplayScreen(); + private void selectedItemChanged() { updateWorkingBeatmap(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 9a68ff908d..c5d7610b53 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -416,10 +416,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer UpdateMods(); if (client.LocalUser.State == MultiplayerUserState.Spectating - && (client.Room.State == MultiplayerRoomState.Playing || client.Room.State == MultiplayerRoomState.WaitingForLoad) - && ParentScreen.IsCurrentScreen()) + && (client.Room.State == MultiplayerRoomState.Playing || client.Room.State == MultiplayerRoomState.WaitingForLoad)) { - PushTopLevelScreen(() => new MultiplayerSpectator(client.CurrentMatchPlayingUserIds.ToArray())); + StartPlay(); // If the current user was host, they started the match and the in-progres operation needs to be stopped now. readyClickOperation?.Dispose(); @@ -429,16 +428,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onLoadRequested() { - Debug.Assert(client.Room != null); - - int[] userIds = client.CurrentMatchPlayingUserIds.ToArray(); - - StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds)); + StartPlay(); readyClickOperation?.Dispose(); readyClickOperation = null; } + protected override Screen CreateGameplayScreen() + { + Debug.Assert(client.LocalUser != null); + + if (client.LocalUser.State == MultiplayerUserState.Spectating) + return new MultiSpectatorScreen(client.CurrentMatchPlayingUserIds.ToArray()); + + return new MultiplayerPlayer(SelectedItem.Value, client.CurrentMatchPlayingUserIds.ToArray()); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 6542d01e64..11bc55823f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -218,10 +218,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, new Drawable[] { - new Footer - { - OnStart = onStart, - } + new Footer { OnStart = StartPlay } } }, RowDimensions = new[] @@ -274,9 +271,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, true); } - private void onStart() => StartPlay(() => new PlaylistsPlayer(SelectedItem.Value) + protected override Screen CreateGameplayScreen() => new PlaylistsPlayer(SelectedItem.Value) { Exited = () => leaderboard.RefreshScores() - }); + }; } } From 4aceb75eb2f8c5c40ff796ecc8d8fd22c3a6ddf1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 23:37:45 +0900 Subject: [PATCH 096/708] Disable spectate button on closed rooms Doesn't have an effect normally - only for safety purposes in case we allow entering the match subscreen after a match has finished in the future. --- .../OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 465af037e2..04150902bc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match break; } - button.Enabled.Value = !operationInProgress.Value; + button.Enabled.Value = Client.Room?.State != MultiplayerRoomState.Closed && !operationInProgress.Value; } private class ButtonWithTrianglesExposed : TriangleButton From 8a0ba3a05557e3d1f20521e577bb61d7604c9a97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 23:38:51 +0900 Subject: [PATCH 097/708] Merge GameplayIsolationContainer into PlayerInstance, remove track --- .../Spectate/GameplayIsolationContainer.cs | 52 ------------------- .../Multiplayer/Spectate/PlayerInstance.cs | 35 ++++++++++++- 2 files changed, 33 insertions(+), 54 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs deleted file mode 100644 index 7b6a544084..0000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/GameplayIsolationContainer.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Audio.Track; -using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate -{ - public class GameplayIsolationContainer : Container - { - [Cached] - private readonly Bindable ruleset = new Bindable(); - - [Cached] - private readonly Bindable beatmap = new Bindable(); - - [Cached] - private readonly Bindable> mods = new Bindable>(); - - private readonly Track track; - - public GameplayIsolationContainer(WorkingBeatmap beatmap, RulesetInfo ruleset, IReadOnlyList mods) - { - this.beatmap.Value = beatmap; - this.ruleset.Value = ruleset; - this.mods.Value = mods; - - track = beatmap.LoadTrack(); - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(ruleset.BeginLease(false)); - dependencies.CacheAs(beatmap.BeginLease(false)); - dependencies.CacheAs(mods.BeginLease(false)); - return dependencies; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - track?.Dispose(); - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 80d6727599..8007c9e068 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -9,6 +10,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; @@ -58,13 +61,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Score = score; - gameplayContent.Child = new GameplayIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) + gameplayContent.Child = new PlayerIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, Child = stack = new OsuScreenStack() }; - stack.Push(new MultiplayerSpectatorPlayerLoader(Score, () => new MultiplayerSpectatorPlayer(Score, GameplayClock))); + stack.Push(new MultiSpectatorPlayerLoader(Score, () => new MultiSpectatorPlayer(Score, GameplayClock))); loadingLayer.Hide(); } @@ -116,5 +119,33 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public BindableNumber Tempo => audioContainer.Tempo; #endregion + + private class PlayerIsolationContainer : Container + { + [Cached] + private readonly Bindable ruleset = new Bindable(); + + [Cached] + private readonly Bindable beatmap = new Bindable(); + + [Cached] + private readonly Bindable> mods = new Bindable>(); + + public PlayerIsolationContainer(WorkingBeatmap beatmap, RulesetInfo ruleset, IReadOnlyList mods) + { + this.beatmap.Value = beatmap; + this.ruleset.Value = ruleset; + this.mods.Value = mods; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(ruleset.BeginLease(false)); + dependencies.CacheAs(beatmap.BeginLease(false)); + dependencies.CacheAs(mods.BeginLease(false)); + return dependencies; + } + } } } From ee259497519f71d360616b6f2dc92247bfdfe43b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 23:39:02 +0900 Subject: [PATCH 098/708] Rename classes --- .../Multiplayer/TestSceneMultiplayerSpectator.cs | 12 ++++++------ .../TestSceneMultiplayerSpectatorLeaderboard.cs | 4 ++-- ...orLeaderboard.cs => MultiSpectatorLeaderboard.cs} | 4 ++-- ...yerSpectatorPlayer.cs => MultiSpectatorPlayer.cs} | 4 ++-- ...PlayerLoader.cs => MultiSpectatorPlayerLoader.cs} | 4 ++-- ...ltiplayerSpectator.cs => MultiSpectatorScreen.cs} | 8 ++++---- .../Multiplayer/Spectate/Sync/ISlaveClock.cs | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{MultiplayerSpectatorLeaderboard.cs => MultiSpectatorLeaderboard.cs} (92%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{MultiplayerSpectatorPlayer.cs => MultiSpectatorPlayer.cs} (93%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{MultiplayerSpectatorPlayerLoader.cs => MultiSpectatorPlayerLoader.cs} (75%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{MultiplayerSpectator.cs => MultiSpectatorScreen.cs} (93%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs index cd89d3bcc1..0b77a1a9e2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private BeatmapManager beatmapManager { get; set; } - private MultiplayerSpectator spectator; + private MultiSpectatorScreen spectatorScreen; private readonly List playingUserIds = new List(); private readonly Dictionary nextFrame = new Dictionary(); @@ -91,11 +91,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddWaitStep("wait a bit", 10); AddStep("load player 55", () => streamingClient.StartPlay(55, importedBeatmapId)); - AddUntilStep("one player added", () => spectator.ChildrenOfType().Count() == 1); + AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1); AddWaitStep("wait a bit", 10); AddStep("load player 56", () => streamingClient.StartPlay(56, importedBeatmapId)); - AddUntilStep("two players added", () => spectator.ChildrenOfType().Count() == 2); + AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2); } [Test] @@ -239,10 +239,10 @@ namespace osu.Game.Tests.Visual.Multiplayer Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap); Ruleset.Value = importedBeatmap.Ruleset; - LoadScreen(spectator = new MultiplayerSpectator(playingUserIds.ToArray())); + LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUserIds.ToArray())); }); - AddUntilStep("wait for screen load", () => spectator.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectator.AllPlayersLoaded)); + AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); } private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); @@ -299,7 +299,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); - private PlayerInstance getInstance(int userId) => spectator.ChildrenOfType().Single(p => p.UserId == userId); + private PlayerInstance getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); public class TestSpectatorStreamingClient : SpectatorStreamingClient { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs index 3b2cfb1c7b..8368186219 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUpSteps] public new void SetUpSteps() { - MultiplayerSpectatorLeaderboard leaderboard = null; + MultiSpectatorLeaderboard leaderboard = null; AddStep("reset", () => { @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var scoreProcessor = new OsuScoreProcessor(); scoreProcessor.ApplyBeatmap(playable); - LoadComponentAsync(leaderboard = new MultiplayerSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add); + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add); }); AddUntilStep("wait for load", () => leaderboard.IsLoaded); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs similarity index 92% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs index 1b9e2bda2d..96c7702048 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs @@ -9,9 +9,9 @@ using osu.Game.Screens.Play.HUD; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public class MultiplayerSpectatorLeaderboard : MultiplayerGameplayLeaderboard + public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard { - public MultiplayerSpectatorLeaderboard(ScoreProcessor scoreProcessor, int[] userIds) + public MultiSpectatorLeaderboard(ScoreProcessor scoreProcessor, int[] userIds) : base(scoreProcessor, userIds) { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs similarity index 93% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 69b38c61f7..0101c00041 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -11,13 +11,13 @@ using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public class MultiplayerSpectatorPlayer : SpectatorPlayer + public class MultiSpectatorPlayer : SpectatorPlayer { private readonly Bindable waitingOnFrames = new Bindable(true); private readonly Score score; private readonly ISlaveClock slaveClock; - public MultiplayerSpectatorPlayer(Score score, ISlaveClock slaveClock) + public MultiSpectatorPlayer(Score score, ISlaveClock slaveClock) : base(score) { this.score = score; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayerLoader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs similarity index 75% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayerLoader.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs index c8f16b5e90..fce44296f3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayerLoader.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs @@ -8,9 +8,9 @@ using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public class MultiplayerSpectatorPlayerLoader : SpectatorPlayerLoader + public class MultiSpectatorPlayerLoader : SpectatorPlayerLoader { - public MultiplayerSpectatorPlayerLoader(Score score, Func createPlayer) + public MultiSpectatorPlayerLoader(Score score, Func createPlayer) : base(score, createPlayer) { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs similarity index 93% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 33dd57586c..52b7898028 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -14,7 +14,7 @@ using osu.Game.Screens.Spectate; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public class MultiplayerSpectator : SpectatorScreen + public class MultiSpectatorScreen : SpectatorScreen { // Isolates beatmap/ruleset to this screen. public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -28,10 +28,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private MasterGameplayClockContainer masterClockContainer; private ISyncManager syncManager; private PlayerGrid grid; - private MultiplayerSpectatorLeaderboard leaderboard; + private MultiSpectatorLeaderboard leaderboard; private PlayerInstance currentAudioSource; - public MultiplayerSpectator(int[] userIds) + public MultiSpectatorScreen(int[] userIds) : base(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) { instances = new PlayerInstance[UserIds.Length]; @@ -76,7 +76,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor(); scoreProcessor.ApplyBeatmap(playableBeatmap); - LoadComponentAsync(leaderboard = new MultiplayerSpectatorLeaderboard(scoreProcessor, UserIds) { Expanded = { Value = true } }, leaderboardContainer.Add); + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, UserIds) { Expanded = { Value = true } }, leaderboardContainer.Add); } protected override void LoadComplete() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs index f45cb1fde6..08d69f9560 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs @@ -7,7 +7,7 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { /// - /// A clock which is used by s and managed by an . + /// A clock which is used by s and managed by an . /// public interface ISlaveClock : IFrameBasedClock, IAdjustableClock { From 4f0857f946e96d5c0c9da4440587e09488ca083e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 23:52:22 +0900 Subject: [PATCH 099/708] Xmldocs and general refactorings --- .../TestSceneMultiplayerSpectator.cs | 2 +- .../Spectate/MultiSpectatorLeaderboard.cs | 2 +- .../Spectate/MultiSpectatorPlayer.cs | 17 +++++++++-- .../Spectate/MultiSpectatorPlayerLoader.cs | 6 +++- .../Spectate/MultiSpectatorScreen.cs | 18 +++++++++--- .../{PlayerInstance.cs => PlayerArea.cs} | 29 +++++++++++++++---- 6 files changed, 59 insertions(+), 15 deletions(-) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{PlayerInstance.cs => PlayerArea.cs} (83%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs index 0b77a1a9e2..a82dea5640 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs @@ -299,7 +299,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); - private PlayerInstance getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); + private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); public class TestSpectatorStreamingClient : SpectatorStreamingClient { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs index 96c7702048..ab3ead68b5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard { - public MultiSpectatorLeaderboard(ScoreProcessor scoreProcessor, int[] userIds) + public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, int[] userIds) : base(scoreProcessor, userIds) { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 0101c00041..4ed949893c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Timing; @@ -11,13 +12,21 @@ using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { + /// + /// A single spectated player within a . + /// public class MultiSpectatorPlayer : SpectatorPlayer { private readonly Bindable waitingOnFrames = new Bindable(true); private readonly Score score; private readonly ISlaveClock slaveClock; - public MultiSpectatorPlayer(Score score, ISlaveClock slaveClock) + /// + /// Creates a new . + /// + /// The score containing the player's replay. + /// The clock controlling the gameplay running state. + public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISlaveClock slaveClock) : base(score) { this.score = score; @@ -33,6 +42,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); + + // This is required because the frame stable clock is set to WaitingOnFrames = false for one frame. waitingOnFrames.Value = DrawableRuleset.FrameStableClock.WaitingOnFrames.Value || score.Replay.Frames.Count == 0; } @@ -41,14 +52,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private class SlaveGameplayClockContainer : GameplayClockContainer { - public SlaveGameplayClockContainer(IClock sourceClock) + public SlaveGameplayClockContainer([NotNull] IClock sourceClock) : base(sourceClock) { } protected override void Update() { - // The slave clock's running state is controlled by the sync manager, but the local pausing state needs to be updated to stop gameplay. + // The slave clock's running state is controlled externally, but the local pausing state needs to be updated to stop gameplay. if (SourceClock.IsRunning) Start(); else diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs index fce44296f3..5a1d28e9c4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayerLoader.cs @@ -2,15 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { + /// + /// Used to load a single in a . + /// public class MultiSpectatorPlayerLoader : SpectatorPlayerLoader { - public MultiSpectatorPlayerLoader(Score score, Func createPlayer) + public MultiSpectatorPlayerLoader([NotNull] Score score, [NotNull] Func createPlayer) : base(score, createPlayer) { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 52b7898028..609905a312 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -14,27 +14,37 @@ using osu.Game.Screens.Spectate; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { + /// + /// A that spectates multiple users in a match. + /// public class MultiSpectatorScreen : SpectatorScreen { // Isolates beatmap/ruleset to this screen. public override bool DisallowExternalBeatmapRulesetChanges => true; + /// + /// Whether all spectating players have finished loading. + /// public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); [Resolved] private SpectatorStreamingClient spectatorClient { get; set; } - private readonly PlayerInstance[] instances; + private readonly PlayerArea[] instances; private MasterGameplayClockContainer masterClockContainer; private ISyncManager syncManager; private PlayerGrid grid; private MultiSpectatorLeaderboard leaderboard; - private PlayerInstance currentAudioSource; + private PlayerArea currentAudioSource; + /// + /// Creates a new . + /// + /// The players to spectate. public MultiSpectatorScreen(int[] userIds) : base(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) { - instances = new PlayerInstance[UserIds.Length]; + instances = new PlayerArea[UserIds.Length]; } [BackgroundDependencyLoader] @@ -69,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }; for (int i = 0; i < UserIds.Length; i++) - grid.Add(instances[i] = new PlayerInstance(UserIds[i], new CatchUpSlaveClock(masterClockContainer.GameplayClock))); + grid.Add(instances[i] = new PlayerArea(UserIds[i], new CatchUpSlaveClock(masterClockContainer.GameplayClock))); // Todo: This is not quite correct - it should be per-user to adjust for other mod combinations. var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs similarity index 83% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 8007c9e068..494f182251 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -18,13 +19,31 @@ using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public class PlayerInstance : CompositeDrawable, IAdjustableAudioComponent + /// + /// Provides an area for and manages the hierarchy of a spectated player within a . + /// + public class PlayerArea : CompositeDrawable, IAdjustableAudioComponent { + /// + /// Whether a is loaded in the area. + /// public bool PlayerLoaded => stack?.CurrentScreen is Player; + /// + /// The user id this corresponds to. + /// public readonly int UserId; - public readonly CatchUpSlaveClock GameplayClock; + /// + /// The used to control the gameplay running state of a loaded . + /// + [NotNull] + public readonly ISlaveClock GameplayClock; + + /// + /// The currently-loaded score. + /// + [CanBeNull] public Score Score { get; private set; } [Resolved] @@ -35,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly AudioContainer audioContainer; private OsuScreenStack stack; - public PlayerInstance(int userId, CatchUpSlaveClock gameplayClock) + public PlayerArea(int userId, [NotNull] ISlaveClock gameplayClock) { UserId = userId; GameplayClock = gameplayClock; @@ -54,10 +73,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }; } - public void LoadScore(Score score) + public void LoadScore([NotNull] Score score) { if (Score != null) - throw new InvalidOperationException($"Cannot load a new score on a {nameof(PlayerInstance)} with an existing score."); + throw new InvalidOperationException($"Cannot load a new score on a {nameof(PlayerArea)} that has an existing score."); Score = score; From 90ecda91af351c248882469195f4a964dd2636bf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Apr 2021 00:06:54 +0900 Subject: [PATCH 100/708] Fix exception --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index c5d7610b53..aac03622e3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -416,7 +416,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer UpdateMods(); if (client.LocalUser.State == MultiplayerUserState.Spectating - && (client.Room.State == MultiplayerRoomState.Playing || client.Room.State == MultiplayerRoomState.WaitingForLoad)) + && (client.Room.State == MultiplayerRoomState.Playing || client.Room.State == MultiplayerRoomState.WaitingForLoad) + && ParentScreen.IsCurrentScreen()) { StartPlay(); From b25340653df7e657884cfd077c53eea43613f25b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Apr 2021 00:49:14 +0900 Subject: [PATCH 101/708] Fix failing tests --- .../TestSceneMultiplayerSpectateButton.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index e65e4a68a7..afbc9c32b3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -120,9 +120,12 @@ namespace osu.Game.Tests.Visual.Multiplayer }; }); - [Test] - public void TestEnabledWhenRoomOpen() + [TestCase(MultiplayerRoomState.Open)] + [TestCase(MultiplayerRoomState.WaitingForLoad)] + [TestCase(MultiplayerRoomState.Playing)] + public void TestEnabledWhenRoomOpenOrInGameplay(MultiplayerRoomState roomState) { + AddStep($"change room to {roomState}", () => Client.ChangeRoomState(roomState)); assertSpectateButtonEnablement(true); } @@ -137,12 +140,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); } - [TestCase(MultiplayerRoomState.WaitingForLoad)] - [TestCase(MultiplayerRoomState.Playing)] [TestCase(MultiplayerRoomState.Closed)] - public void TestDisabledDuringGameplayOrClosed(MultiplayerRoomState roomState) + public void TestDisabledWhenClosed(MultiplayerRoomState roomState) { - AddStep($"change user to {roomState}", () => Client.ChangeRoomState(roomState)); + AddStep($"change room to {roomState}", () => Client.ChangeRoomState(roomState)); assertSpectateButtonEnablement(false); } From 713344ebadc752b16c83bab92331af3c5850be25 Mon Sep 17 00:00:00 2001 From: Denrage Date: Fri, 23 Apr 2021 10:31:49 +0200 Subject: [PATCH 102/708] 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 575ec7c528af97d00e7aa3e3fce940ae5e64350f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Apr 2021 19:09:54 +0900 Subject: [PATCH 103/708] Document + refactor max player limitation --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 609905a312..eb59d090be 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// The players to spectate. public MultiSpectatorScreen(int[] userIds) - : base(userIds.AsSpan().Slice(0, Math.Min(16, userIds.Length)).ToArray()) + : base(userIds.Take(PlayerGrid.MAX_PLAYERS).ToArray()) { instances = new PlayerArea[UserIds.Length]; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs index 830378f129..6638d47dca 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -15,6 +15,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public partial class PlayerGrid : CompositeDrawable { + /// + /// A temporary limitation on the number of players, because only layouts up to 16 players are supported for a single screen. + /// Todo: Can be removed in the future with scrolling support + performance improvements. + /// + public const int MAX_PLAYERS = 16; + private const float player_spacing = 5; /// @@ -58,11 +64,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// Adds a new cell with content to this grid. /// /// The content the cell should contain. - /// If more than 16 cells are added. + /// If more than cells are added. public void Add(Drawable content) { - if (cellContainer.Count == 16) - throw new InvalidOperationException("Only 16 cells are supported."); + if (cellContainer.Count == MAX_PLAYERS) + throw new InvalidOperationException($"Only {MAX_PLAYERS} cells are supported."); int index = cellContainer.Count; From 63a948425513a3bba7c660850a5d562bf3efc3ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Apr 2021 19:11:47 +0900 Subject: [PATCH 104/708] Expose WaitingOnFrames as mutable bindable --- osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs | 3 +-- .../OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs index a8c0a763e9..2c10be9c90 100644 --- a/osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs @@ -164,8 +164,7 @@ namespace osu.Game.Tests.OnlinePlay private class TestSlaveClock : TestManualClock, ISlaveClock { - public readonly Bindable WaitingOnFrames = new Bindable(true); - IBindable ISlaveClock.WaitingOnFrames => WaitingOnFrames; + public Bindable WaitingOnFrames { get; } = new Bindable(true); public bool IsCatchingUp { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs index cefe5ff04f..21241f8158 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync public FrameTimeInfo TimeInfo => new FrameTimeInfo { Elapsed = ElapsedFrameTime, Current = CurrentTime }; - public IBindable WaitingOnFrames { get; } = new Bindable(true); + public Bindable WaitingOnFrames { get; } = new Bindable(true); public bool IsCatchingUp { get; set; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs index 08d69f9560..6b7a4f5221 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync /// /// Whether this clock is waiting on frames to continue playback. /// - IBindable WaitingOnFrames { get; } + Bindable WaitingOnFrames { get; } /// /// Whether this clock is resynchronising to the master clock. From b18635341e7692dfbfcbc7e3b35b0efd23a409e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Apr 2021 19:12:30 +0900 Subject: [PATCH 105/708] Rename file --- ...CaseCatchUpSyncManager.cs => TestSceneCatchUpSyncManager.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/OnlinePlay/{TestCaseCatchUpSyncManager.cs => TestSceneCatchUpSyncManager.cs} (99%) diff --git a/osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs similarity index 99% rename from osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs rename to osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index 2c10be9c90..93801bd5d7 100644 --- a/osu.Game.Tests/OnlinePlay/TestCaseCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.OnlinePlay { [HeadlessTest] - public class TestCaseCatchUpSyncManager : OsuTestScene + public class TestSceneCatchUpSyncManager : OsuTestScene { private TestManualClock master; private CatchUpSyncManager syncManager; From b41897fd9b34e09ba252638c75d0173e61f7c94a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Apr 2021 19:23:52 +0900 Subject: [PATCH 106/708] Rename testscene to match class --- ...MultiplayerSpectator.cs => TestSceneMultiSpectatorScreen.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Multiplayer/{TestSceneMultiplayerSpectator.cs => TestSceneMultiSpectatorScreen.cs} (99%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs similarity index 99% rename from osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index a82dea5640..9cc8eaf876 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -23,7 +23,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerSpectator : MultiplayerTestScene + public class TestSceneMultiSpectatorScreen : MultiplayerTestScene { [Cached(typeof(SpectatorStreamingClient))] private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient(); From decd8803bc6daa0669003e22115672dbda48033f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Apr 2021 07:44:33 +0300 Subject: [PATCH 107/708] Abstract base appearence and hover update from drag handles --- .../Compose/Components/SelectionBoxControl.cs | 96 +++++++++++++++++++ .../Components/SelectionBoxDragHandle.cs | 77 +-------------- .../Components/SelectionBoxScaleHandle.cs | 17 ++++ 3 files changed, 116 insertions(+), 74 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs new file mode 100644 index 0000000000..3432334deb --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// Represents the base appearance for UI controls of the , + /// such as scale handles, rotation handles, buttons, etc... + /// + public abstract class SelectionBoxControl : CompositeDrawable + { + public event Action OperationStarted; + public event Action OperationEnded; + + internal event Action HoverGained; + internal event Action HoverLost; + + private Circle circle; + + [Resolved] + protected OsuColour Colours { get; private set; } + + [BackgroundDependencyLoader] + private void load() + { + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + UpdateHoverState(); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateHoverState(); + HoverGained?.Invoke(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + HoverLost?.Invoke(); + UpdateHoverState(); + } + + /// + /// Whether this control is currently handling mouse down input. + /// + protected bool HandlingMouse { get; set; } + + protected override bool OnMouseDown(MouseDownEvent e) + { + HandlingMouse = true; + UpdateHoverState(); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + HandlingMouse = false; + UpdateHoverState(); + base.OnMouseUp(e); + } + + protected virtual void UpdateHoverState() + { + circle.Colour = HandlingMouse ? Colours.GrayF : (IsHovered ? Colours.Red : Colours.YellowDark); + this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint); + } + + protected void OnOperationStarted() => OperationStarted?.Invoke(); + + protected void OnOperationEnded() => OperationEnded?.Invoke(); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs index 921b4eb042..ea4f9704ef 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs @@ -2,75 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public class SelectionBoxDragHandle : Container + public abstract class SelectionBoxDragHandle : SelectionBoxControl { - public Action OperationStarted; - public Action OperationEnded; - public Action HandleDrag { get; set; } - private Circle circle; - - [Resolved] - private OsuColour colours { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - Size = new Vector2(10); - Origin = Anchor.Centre; - - InternalChildren = new Drawable[] - { - circle = new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - UpdateHoverState(); - } - - protected override bool OnHover(HoverEvent e) - { - UpdateHoverState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - UpdateHoverState(); - } - - protected bool HandlingMouse; - - protected override bool OnMouseDown(MouseDownEvent e) - { - HandlingMouse = true; - UpdateHoverState(); - return true; - } - protected override bool OnDragStart(DragStartEvent e) { - OperationStarted?.Invoke(); + OnOperationStarted(); return true; } @@ -83,23 +25,10 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void OnDragEnd(DragEndEvent e) { HandlingMouse = false; - OperationEnded?.Invoke(); + OnOperationEnded(); UpdateHoverState(); base.OnDragEnd(e); } - - protected override void OnMouseUp(MouseUpEvent e) - { - HandlingMouse = false; - UpdateHoverState(); - base.OnMouseUp(e); - } - - protected virtual void UpdateHoverState() - { - circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark); - this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint); - } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs new file mode 100644 index 0000000000..a87c661f45 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.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.Framework.Allocation; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class SelectionBoxScaleHandle : SelectionBoxDragHandle + { + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(10); + } + } +} From 4bfa9cd6b6014a40666bf00492c95c8d888560f7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Apr 2021 07:46:38 +0300 Subject: [PATCH 108/708] Change inheritance of selection box buttons to base control instead --- ...BoxDragHandleButton.cs => SelectionBoxButton.cs} | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) rename osu.Game/Screens/Edit/Compose/Components/{SelectionBoxDragHandleButton.cs => SelectionBoxButton.cs} (77%) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs similarity index 77% rename from osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleButton.cs rename to osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 74ae949389..917e5189b2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -12,10 +12,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components { - /// - /// A drag "handle" which shares the visual appearance but behaves more like a clickable button. - /// - public sealed class SelectionBoxDragHandleButton : SelectionBoxDragHandle, IHasTooltip + public sealed class SelectionBoxButton : SelectionBoxControl, IHasTooltip { private SpriteIcon icon; @@ -23,7 +20,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public Action Action; - public SelectionBoxDragHandleButton(IconUsage iconUsage, string tooltip) + public SelectionBoxButton(IconUsage iconUsage, string tooltip) { this.iconUsage = iconUsage; @@ -36,7 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - Size *= 2; + Size = new Vector2(20); AddInternal(icon = new SpriteIcon { RelativeSizeAxes = Axes.Both, @@ -49,9 +46,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnClick(ClickEvent e) { - OperationStarted?.Invoke(); + OnOperationStarted(); Action?.Invoke(); - OperationEnded?.Invoke(); + OnOperationEnded(); return true; } From 206fc94b8c9e71be4dc3049003f63147bf735a22 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Apr 2021 07:47:15 +0300 Subject: [PATCH 109/708] Add rotation drag handle component --- .../Components/SelectionBoxRotationHandle.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs new file mode 100644 index 0000000000..c4a4fafdc2 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class SelectionBoxRotationHandle : SelectionBoxDragHandle + { + private SpriteIcon icon; + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(15f); + AddInternal(icon = new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.Redo, + Scale = new Vector2 + { + X = Anchor.HasFlagFast(Anchor.x0) ? 1f : -1f, + Y = Anchor.HasFlagFast(Anchor.y0) ? 1f : -1f + } + }); + } + + protected override void UpdateHoverState() + { + icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black; + base.UpdateHoverState(); + } + } +} From 7e3a611f95cf9a1ec5503d692bc919c6dc6fb71c Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 16:23:52 +0800 Subject: [PATCH 110/708] 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 111/708] 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 112/708] 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 113/708] 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 a8b401522bf561d5a2d49567ec99a2048e56586a Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Sat, 24 Apr 2021 20:39:22 +0800 Subject: [PATCH 114/708] 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 115/708] 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 116/708] 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 117/708] 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 118/708] 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 119/708] 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 120/708] 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 121/708] 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 122/708] 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 123/708] 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 124/708] 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 125/708] 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 126/708] 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 127/708] 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 62bcc5f76d891f45addc79500350be050f3b7fe9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Apr 2021 07:49:31 +0300 Subject: [PATCH 128/708] Add property for tracking whether control is during operation --- .../Compose/Components/SelectionBoxControl.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 3432334deb..081848f372 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -25,6 +25,11 @@ namespace osu.Game.Screens.Edit.Compose.Components private Circle circle; + /// + /// Whether an operation has began from this control. + /// + public bool InOperation { get; private set; } + [Resolved] protected OsuColour Colours { get; private set; } @@ -89,8 +94,16 @@ namespace osu.Game.Screens.Edit.Compose.Components this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint); } - protected void OnOperationStarted() => OperationStarted?.Invoke(); + protected void OnOperationStarted() + { + InOperation = true; + OperationStarted?.Invoke(); + } - protected void OnOperationEnded() => OperationEnded?.Invoke(); + protected void OnOperationEnded() + { + InOperation = false; + OperationEnded?.Invoke(); + } } } From ab7178267459a27b5a3a9896013cd09fe250dac9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Apr 2021 07:43:56 +0300 Subject: [PATCH 129/708] Use colour fade transform for selection box controls To become harminous with the fade transforms of the rotation control --- .../Edit/Compose/Components/SelectionBoxButton.cs | 2 +- .../Edit/Compose/Components/SelectionBoxControl.cs | 10 ++++++++-- .../Compose/Components/SelectionBoxRotationHandle.cs | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 917e5189b2..fbbebe3288 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void UpdateHoverState() { base.UpdateHoverState(); - icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black; + icon.FadeColour(!HandlingMouse && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } public string TooltipText { get; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 081848f372..374af20742 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -17,6 +17,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public abstract class SelectionBoxControl : CompositeDrawable { + public const double TRANSFORM_DURATION = 100; + public event Action OperationStarted; public event Action OperationEnded; @@ -90,8 +92,12 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual void UpdateHoverState() { - circle.Colour = HandlingMouse ? Colours.GrayF : (IsHovered ? Colours.Red : Colours.YellowDark); - this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint); + if (HandlingMouse) + circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint); + else + circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint); + + this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint); } protected void OnOperationStarted() diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index c4a4fafdc2..6303caf9ed 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -35,8 +35,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void UpdateHoverState() { - icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black; base.UpdateHoverState(); + icon.FadeColour(!HandlingMouse && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } } } From 77f7d4c9632e281d8665a81c55252f12b181e163 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Apr 2021 08:06:00 +0300 Subject: [PATCH 130/708] Add composite managing display of selection box drag handles --- .../SelectionBoxDragHandleDisplay.cs | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleDisplay.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleDisplay.cs new file mode 100644 index 0000000000..1cba8ca6b3 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleDisplay.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// Represents a display composite containing and managing the visibility state of the selection box's drag handles. + /// + public class SelectionBoxDragHandleDisplay : CompositeDrawable + { + private Container scaleHandles; + private Container rotationHandles; + + private readonly List allDragHandles = new List(); + + public new MarginPadding Padding + { + get => base.Padding; + set => base.Padding = value; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + scaleHandles = new Container + { + RelativeSizeAxes = Axes.Both, + }, + rotationHandles = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(-12.5f), + }, + }; + } + + public void AddScaleHandle(SelectionBoxScaleHandle handle) + { + bindDragHandle(handle); + scaleHandles.Add(handle); + } + + public void AddRotationHandle(SelectionBoxRotationHandle handle) + { + handle.Alpha = 0; + handle.AlwaysPresent = true; + + bindDragHandle(handle); + rotationHandles.Add(handle); + } + + private void bindDragHandle(SelectionBoxDragHandle handle) + { + handle.HoverGained += updateRotationHandlesVisibility; + handle.HoverLost += updateRotationHandlesVisibility; + handle.OperationStarted += updateRotationHandlesVisibility; + handle.OperationEnded += updateRotationHandlesVisibility; + allDragHandles.Add(handle); + } + + private SelectionBoxRotationHandle displayedRotationHandle; + private SelectionBoxDragHandle activeHandle; + + private void updateRotationHandlesVisibility() + { + if (activeHandle?.InOperation == true || activeHandle?.IsHovered == true) + return; + + displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint); + displayedRotationHandle = null; + + activeHandle = allDragHandles.SingleOrDefault(h => h.InOperation); + activeHandle ??= allDragHandles.SingleOrDefault(h => h.IsHovered); + + if (activeHandle != null) + { + displayedRotationHandle = getCorrespondingRotationHandle(activeHandle, rotationHandles); + displayedRotationHandle?.FadeIn(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint); + } + } + + /// + /// Gets the rotation handle corresponding to the given handle. + /// + [CanBeNull] + private static SelectionBoxRotationHandle getCorrespondingRotationHandle(SelectionBoxDragHandle handle, IEnumerable rotationHandles) + { + if (handle is SelectionBoxRotationHandle rotationHandle) + return rotationHandle; + + return rotationHandles.SingleOrDefault(r => r.Anchor == handle.Anchor); + } + } +} From a8c460f7df0924830c3ad7c3b657a04eda8ed4c7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Apr 2021 08:18:46 +0300 Subject: [PATCH 131/708] Replace separated top-centered rotation button with rotation drag handles --- .../Edit/Compose/Components/SelectionBox.cs | 87 ++++++++++--------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 9d6b44e207..d7db98f6a2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private Container dragHandles; + private SelectionBoxDragHandleDisplay dragHandles; private FillFlowContainer buttons; public const float BORDER_RADIUS = 3; @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }, } }, - dragHandles = new Container + dragHandles = new SelectionBoxDragHandleDisplay { RelativeSizeAxes = Axes.Both, // ensures that the centres of all drag handles line up with the middle of the selection box border. @@ -186,75 +186,76 @@ namespace osu.Game.Screens.Edit.Compose.Components private void addRotationComponents() { - const float separation = 40; - addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90)); addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90)); - AddRangeInternal(new Drawable[] - { - new Box - { - Depth = float.MaxValue, - Colour = colours.YellowLight, - Blending = BlendingParameters.Additive, - Alpha = 0.3f, - Size = new Vector2(BORDER_RADIUS, separation), - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - }, - new SelectionBoxDragHandleButton(FontAwesome.Solid.Redo, "Free rotate") - { - Anchor = Anchor.TopCentre, - Y = -separation, - HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)), - OperationStarted = operationStarted, - OperationEnded = operationEnded - } - }); + addRotateHandle(Anchor.TopLeft); + addRotateHandle(Anchor.TopRight); + addRotateHandle(Anchor.BottomLeft); + addRotateHandle(Anchor.BottomRight); } private void addYScaleComponents() { addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical)); - addDragHandle(Anchor.TopCentre); - addDragHandle(Anchor.BottomCentre); + addScaleHandle(Anchor.TopCentre); + addScaleHandle(Anchor.BottomCentre); } private void addFullScaleComponents() { - addDragHandle(Anchor.TopLeft); - addDragHandle(Anchor.TopRight); - addDragHandle(Anchor.BottomLeft); - addDragHandle(Anchor.BottomRight); + addScaleHandle(Anchor.TopLeft); + addScaleHandle(Anchor.TopRight); + addScaleHandle(Anchor.BottomLeft); + addScaleHandle(Anchor.BottomRight); } private void addXScaleComponents() { addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal)); - addDragHandle(Anchor.CentreLeft); - addDragHandle(Anchor.CentreRight); + addScaleHandle(Anchor.CentreLeft); + addScaleHandle(Anchor.CentreRight); } private void addButton(IconUsage icon, string tooltip, Action action) { - buttons.Add(new SelectionBoxDragHandleButton(icon, tooltip) + var button = new SelectionBoxButton(icon, tooltip) { - OperationStarted = operationStarted, - OperationEnded = operationEnded, Action = action - }); + }; + + button.OperationStarted += operationStarted; + button.OperationEnded += operationEnded; + buttons.Add(button); } - private void addDragHandle(Anchor anchor) => dragHandles.Add(new SelectionBoxDragHandle + private void addScaleHandle(Anchor anchor) { - Anchor = anchor, - HandleDrag = e => OnScale?.Invoke(e.Delta, anchor), - OperationStarted = operationStarted, - OperationEnded = operationEnded - }); + var handle = new SelectionBoxScaleHandle + { + Anchor = anchor, + HandleDrag = e => OnScale?.Invoke(e.Delta, anchor) + }; + + handle.OperationStarted += operationStarted; + handle.OperationEnded += operationEnded; + dragHandles.AddScaleHandle(handle); + } + + private void addRotateHandle(Anchor anchor) + { + var handle = new SelectionBoxRotationHandle + { + Anchor = anchor, + HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)) + }; + + handle.OperationStarted += operationStarted; + handle.OperationEnded += operationEnded; + dragHandles.AddRotationHandle(handle); + } private int activeOperations; From 11318fd9fcbf8ffc4b9830dd4285037237e0f7b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Apr 2021 09:35:02 +0300 Subject: [PATCH 132/708] Add test coverage --- .../Editing/TestSceneComposeSelectBox.cs | 98 ++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index cf5f1b8818..13b563619a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -1,21 +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 System.Linq; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Screens.Edit.Compose.Components; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { - public class TestSceneComposeSelectBox : OsuTestScene + public class TestSceneComposeSelectBox : OsuManualInputManagerTestScene { private Container selectionArea; + private SelectionBox selectionBox; public TestSceneComposeSelectBox() { - SelectionBox selectionBox = null; - AddStep("create box", () => Child = selectionArea = new Container { @@ -68,5 +71,94 @@ namespace osu.Game.Tests.Visual.Editing selectionArea.Rotation += angle; return true; } + + [SetUp] + public void SetUp() => Schedule(() => + { + InputManager.MoveMouseTo(selectionBox); + selectionBox.CanRotate = true; + selectionBox.CanScaleX = true; + selectionBox.CanScaleY = true; + }); + + [Test] + public void TestRotationHandleShownOnHover() + { + SelectionBoxRotationHandle rotationHandle = null; + + AddStep("enable rotation", () => selectionBox.CanRotate = true); + AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType().First()); + + AddAssert("handle hidden", () => rotationHandle.Alpha == 0); + AddStep("hover over handle", () => InputManager.MoveMouseTo(rotationHandle)); + AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1); + + AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox)); + AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0); + } + + [Test] + public void TestRotationHandleShownOnHoveringClosestScaleHandler() + { + SelectionBoxRotationHandle rotationHandle = null; + + AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType().First()); + + AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0); + AddStep("hover over closest scale handle", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single(s => s.Anchor == rotationHandle.Anchor)); + }); + AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1); + + AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox)); + AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0); + } + + [Test] + public void TestHoverRotationHandleFromScaleHandle() + { + SelectionBoxRotationHandle rotationHandle = null; + + AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType().First()); + + AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0); + AddStep("hover over closest scale handle", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single(s => s.Anchor == rotationHandle.Anchor)); + }); + AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1); + AddAssert("rotation handle not hovered", () => !rotationHandle.IsHovered); + + AddStep("hover over rotation handle", () => InputManager.MoveMouseTo(rotationHandle)); + AddAssert("rotation handle still shown", () => rotationHandle.Alpha == 1); + AddAssert("rotation handle hovered", () => rotationHandle.IsHovered); + + AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox)); + AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0); + } + + [Test] + public void TestDraggingScaleHandleKeepsCorrespondingRotationHandleShown() + { + SelectionBoxRotationHandle rotationHandle = null; + + AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType().First()); + + AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0); + AddStep("hover over and hold closest scale handle", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single(s => s.Anchor == rotationHandle.Anchor)); + InputManager.PressButton(MouseButton.Left); + }); + AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1); + + AddStep("drag to centre", () => InputManager.MoveMouseTo(selectionBox)); + AddAssert("rotation handle still shown", () => rotationHandle.Alpha > 0); + + AddStep("unhold left", () => InputManager.ReleaseButton(MouseButton.Left)); + AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox, new Vector2(20))); + AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0); + } } } 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 133/708] 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 134/708] 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 135/708] 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 136/708] 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 79e2b232d8a18775731bd3cca9c479d787ca64b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Apr 2021 19:26:53 +0300 Subject: [PATCH 137/708] Improve English MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 374af20742..9ad30a366d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private Circle circle; /// - /// Whether an operation has began from this control. + /// Whether this control is currently being operated on by the user. /// public bool InOperation { get; private set; } From 7490511ebf4b869a1b98793ac773fb92b1f6714e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Apr 2021 19:57:16 +0300 Subject: [PATCH 138/708] Instantiate selection box on SetUp --- .../Editing/TestSceneComposeSelectBox.cs | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 13b563619a..7b939245a4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -17,32 +17,30 @@ namespace osu.Game.Tests.Visual.Editing private Container selectionArea; private SelectionBox selectionBox; - public TestSceneComposeSelectBox() + [SetUp] + public void SetUp() => Schedule(() => { - AddStep("create box", () => - Child = selectionArea = new Container + Child = selectionArea = new Container + { + Size = new Vector2(400), + Position = -new Vector2(150), + Anchor = Anchor.Centre, + Children = new Drawable[] { - Size = new Vector2(400), - Position = -new Vector2(150), - Anchor = Anchor.Centre, - Children = new Drawable[] + selectionBox = new SelectionBox { - selectionBox = new SelectionBox - { - CanRotate = true, - CanScaleX = true, - CanScaleY = true, + CanRotate = true, + CanScaleX = true, + CanScaleY = true, - OnRotation = handleRotation, - OnScale = handleScale - } + OnRotation = handleRotation, + OnScale = handleScale } - }); + } + }; - AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state); - AddToggleStep("toggle x", state => selectionBox.CanScaleX = state); - AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); - } + InputManager.MoveMouseTo(selectionBox); + }); private bool handleScale(Vector2 amount, Anchor reference) { @@ -72,15 +70,6 @@ namespace osu.Game.Tests.Visual.Editing return true; } - [SetUp] - public void SetUp() => Schedule(() => - { - InputManager.MoveMouseTo(selectionBox); - selectionBox.CanRotate = true; - selectionBox.CanScaleX = true; - selectionBox.CanScaleY = true; - }); - [Test] public void TestRotationHandleShownOnHover() { From c58ef4230da768aad729e4785a156c80e83bad54 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Apr 2021 19:58:50 +0300 Subject: [PATCH 139/708] Inherit `VisibilityContainer` and make duration constant protected --- .../Edit/Compose/Components/SelectionBoxControl.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 9ad30a366d..501ce6381a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -15,9 +15,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Represents the base appearance for UI controls of the , /// such as scale handles, rotation handles, buttons, etc... /// - public abstract class SelectionBoxControl : CompositeDrawable + public abstract class SelectionBoxControl : VisibilityContainer { - public const double TRANSFORM_DURATION = 100; + protected const double TRANSFORM_DURATION = 100; public event Action OperationStarted; public event Action OperationEnded; @@ -90,6 +90,10 @@ namespace osu.Game.Screens.Edit.Compose.Components base.OnMouseUp(e); } + protected override void PopIn() => this.FadeIn(TRANSFORM_DURATION, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(TRANSFORM_DURATION, Easing.OutQuint); + protected virtual void UpdateHoverState() { if (HandlingMouse) From 7390a12e4b9bda91f0d4f4d374094abaf3fece92 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Apr 2021 19:59:08 +0300 Subject: [PATCH 140/708] SelectionBoxDragHandleDisplay -> SelectionBoxDragHandleContainer --- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 4 ++-- ...gHandleDisplay.cs => SelectionBoxDragHandleContainer.cs} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game/Screens/Edit/Compose/Components/{SelectionBoxDragHandleDisplay.cs => SelectionBoxDragHandleContainer.cs} (92%) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index d7db98f6a2..774ea076a5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private SelectionBoxDragHandleDisplay dragHandles; + private SelectionBoxDragHandleContainer dragHandles; private FillFlowContainer buttons; public const float BORDER_RADIUS = 3; @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }, } }, - dragHandles = new SelectionBoxDragHandleDisplay + dragHandles = new SelectionBoxDragHandleContainer { RelativeSizeAxes = Axes.Both, // ensures that the centres of all drag handles line up with the middle of the selection box border. diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs similarity index 92% rename from osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleDisplay.cs rename to osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs index 1cba8ca6b3..151c169a33 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Represents a display composite containing and managing the visibility state of the selection box's drag handles. /// - public class SelectionBoxDragHandleDisplay : CompositeDrawable + public class SelectionBoxDragHandleContainer : CompositeDrawable { private Container scaleHandles; private Container rotationHandles; @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (activeHandle?.InOperation == true || activeHandle?.IsHovered == true) return; - displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint); + displayedRotationHandle?.Hide(); displayedRotationHandle = null; activeHandle = allDragHandles.SingleOrDefault(h => h.InOperation); @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (activeHandle != null) { displayedRotationHandle = getCorrespondingRotationHandle(activeHandle, rotationHandles); - displayedRotationHandle?.FadeIn(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint); + displayedRotationHandle?.Show(); } } From 334927ed35f3de3edae71467fafe69808c660008 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Apr 2021 20:11:41 +0300 Subject: [PATCH 141/708] Remove leftover step --- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 7b939245a4..914b8b6f27 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -75,7 +75,6 @@ namespace osu.Game.Tests.Visual.Editing { SelectionBoxRotationHandle rotationHandle = null; - AddStep("enable rotation", () => selectionBox.CanRotate = true); AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType().First()); AddAssert("handle hidden", () => rotationHandle.Alpha == 0); From b252485e1ab0dd5bfa38b35ce315219869fb45b0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Apr 2021 20:13:21 +0300 Subject: [PATCH 142/708] Remove protected expose of `HandlingMouse` setter Regardless of `OnDragEnd()`, `OnMouseUp()` will still be called resetting the value of that state back. --- osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs | 2 +- .../Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 501ce6381a..3a2fc7f83a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Whether this control is currently handling mouse down input. /// - protected bool HandlingMouse { get; set; } + protected bool HandlingMouse { get; private set; } protected override bool OnMouseDown(MouseDownEvent e) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs index ea4f9704ef..d7e58df748 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs @@ -24,7 +24,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void OnDragEnd(DragEndEvent e) { - HandlingMouse = false; OnOperationEnded(); UpdateHoverState(); From 485da47d89d9fa4786935fcbd64c9a1d3b6eaac4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 26 Apr 2021 01:41:36 +0300 Subject: [PATCH 143/708] Revert "Inherit `VisibilityContainer` and make duration constant protected" This reverts commit c58ef4230da768aad729e4785a156c80e83bad54. Uhh, how did I push this.. --- .../Edit/Compose/Components/SelectionBoxControl.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 3a2fc7f83a..38ed23fa13 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -15,9 +15,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Represents the base appearance for UI controls of the , /// such as scale handles, rotation handles, buttons, etc... /// - public abstract class SelectionBoxControl : VisibilityContainer + public abstract class SelectionBoxControl : CompositeDrawable { - protected const double TRANSFORM_DURATION = 100; + public const double TRANSFORM_DURATION = 100; public event Action OperationStarted; public event Action OperationEnded; @@ -90,10 +90,6 @@ namespace osu.Game.Screens.Edit.Compose.Components base.OnMouseUp(e); } - protected override void PopIn() => this.FadeIn(TRANSFORM_DURATION, Easing.OutQuint); - - protected override void PopOut() => this.FadeOut(TRANSFORM_DURATION, Easing.OutQuint); - protected virtual void UpdateHoverState() { if (HandlingMouse) From beee318acc92bd0831bcd19984807c7f029e54bf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 26 Apr 2021 01:45:50 +0300 Subject: [PATCH 144/708] Add more distance between each hit object in editor selection test To avoid potentially hovering over the rotation control instead of wherever the test desired to move the mouse to. --- .../Visual/Editing/TestSceneEditorSelection.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs index b82842e30d..4ce1d995a5 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs @@ -50,9 +50,9 @@ namespace osu.Game.Tests.Visual.Editing 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) }, + new HitCircle { StartTime = 200, Position = new Vector2(100) }, + new HitCircle { StartTime = 300, Position = new Vector2(200) }, + new HitCircle { StartTime = 400, Position = new Vector2(300) }, })); AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); @@ -95,9 +95,9 @@ namespace osu.Game.Tests.Visual.Editing var 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) }, + new HitCircle { StartTime = 200, Position = new Vector2(100) }, + new HitCircle { StartTime = 300, Position = new Vector2(200) }, + new HitCircle { StartTime = 400, Position = new Vector2(300) }, }; AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); @@ -131,9 +131,9 @@ namespace osu.Game.Tests.Visual.Editing 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) }, + new HitCircle { StartTime = 200, Position = new Vector2(100) }, + new HitCircle { StartTime = 300, Position = new Vector2(200) }, + new HitCircle { StartTime = 400, Position = new Vector2(300) }, })); moveMouseToObject(() => addedObjects[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 145/708] 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 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 146/708] 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 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 147/708] 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 aa99c192d025240f6571eb87f856d3235e07e91c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 16:21:12 +0900 Subject: [PATCH 148/708] Fix type in inline comment --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index aac03622e3..0cd3b448d2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -421,7 +421,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { StartPlay(); - // If the current user was host, they started the match and the in-progres operation needs to be stopped now. + // If the current user was host, they started the match and the in-progress operation needs to be stopped now. readyClickOperation?.Dispose(); readyClickOperation = null; } From 6d30a1a80f5f7e19893a6340c23aca44b0f20a47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 16:45:20 +0900 Subject: [PATCH 149/708] Reference constant for test startup delay --- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 9cc8eaf876..82bbc7b6d3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -17,6 +17,7 @@ using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Users; @@ -128,7 +129,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestPlayersDoNotStartSimultaneouslyIfBufferingFor15Seconds() + public void TestPlayersDoNotStartSimultaneouslyIfBufferingForMaximumStartDelay() { start(new[] { 55, 56 }); loadSpectateScreen(); @@ -138,8 +139,8 @@ namespace osu.Game.Tests.Visual.Multiplayer checkPausedInstant(55, true); checkPausedInstant(56, true); - // Wait 15 seconds... - AddWaitStep("wait 15 seconds", (int)(15000 / TimePerAction)); + // Wait for the start delay seconds... + AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); // Player 1 should start playing by itself, player 2 should remain paused. checkPausedInstant(55, false); From 55f383c71ea4577017c85b229bfc1127434b229a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 16:48:32 +0900 Subject: [PATCH 150/708] Rename test to match new `MultiSpectatorLeaderboard` class name --- ...orLeaderboard.cs => TestSceneMultiSpectatorLeaderboard.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Tests/Visual/Multiplayer/{TestSceneMultiplayerSpectatorLeaderboard.cs => TestSceneMultiSpectatorLeaderboard.cs} (98%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs similarity index 98% rename from osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 8368186219..da76adf83f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -24,7 +24,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerSpectatorLeaderboard : MultiplayerTestScene + public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene { [Cached(typeof(SpectatorStreamingClient))] private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient(); @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { 56, new ManualClock() } }; - public TestSceneMultiplayerSpectatorLeaderboard() + public TestSceneMultiSpectatorLeaderboard() { base.Content.AddRange(new Drawable[] { From 737a15c2d4c549423d763f29b93680a1391606a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 17:04:39 +0900 Subject: [PATCH 151/708] Extract out test player IDs to constants --- .../TestSceneMultiSpectatorScreen.cs | 133 +++++++++--------- 1 file changed, 68 insertions(+), 65 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 82bbc7b6d3..512311ff03 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -47,6 +47,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private BeatmapInfo importedBeatmap; private int importedBeatmapId; + private const int player_1_id = 55; + private const int player_2_id = 56; + [BackgroundDependencyLoader] private void load() { @@ -80,29 +83,29 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("start players silently", () => { - Client.CurrentMatchPlayingUserIds.Add(55); - Client.CurrentMatchPlayingUserIds.Add(56); - playingUserIds.Add(55); - playingUserIds.Add(56); - nextFrame[55] = 0; - nextFrame[56] = 0; + Client.CurrentMatchPlayingUserIds.Add(player_1_id); + Client.CurrentMatchPlayingUserIds.Add(player_2_id); + playingUserIds.Add(player_1_id); + playingUserIds.Add(player_2_id); + nextFrame[player_1_id] = 0; + nextFrame[player_2_id] = 0; }); loadSpectateScreen(false); AddWaitStep("wait a bit", 10); - AddStep("load player 55", () => streamingClient.StartPlay(55, importedBeatmapId)); + AddStep("load player first_player_id", () => streamingClient.StartPlay(player_1_id, importedBeatmapId)); AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1); AddWaitStep("wait a bit", 10); - AddStep("load player 56", () => streamingClient.StartPlay(56, importedBeatmapId)); + AddStep("load player second_player_id", () => streamingClient.StartPlay(player_2_id, importedBeatmapId)); AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2); } [Test] public void TestGeneral() { - int[] userIds = Enumerable.Range(0, 4).Select(i => 55 + i).ToArray(); + int[] userIds = Enumerable.Range(0, 4).Select(i => player_1_id + i).ToArray(); start(userIds); loadSpectateScreen(); @@ -114,123 +117,123 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPlayersMustStartSimultaneously() { - start(new[] { 55, 56 }); + start(new[] { player_1_id, player_2_id }); loadSpectateScreen(); // Send frames for one player only, both should remain paused. - sendFrames(55, 20); - checkPausedInstant(55, true); - checkPausedInstant(56, true); + sendFrames(player_1_id, 20); + checkPausedInstant(player_1_id, true); + checkPausedInstant(player_2_id, true); // Send frames for the other player, both should now start playing. - sendFrames(56, 20); - checkPausedInstant(55, false); - checkPausedInstant(56, false); + sendFrames(player_2_id, 20); + checkPausedInstant(player_1_id, false); + checkPausedInstant(player_2_id, false); } [Test] public void TestPlayersDoNotStartSimultaneouslyIfBufferingForMaximumStartDelay() { - start(new[] { 55, 56 }); + start(new[] { player_1_id, player_2_id }); loadSpectateScreen(); // Send frames for one player only, both should remain paused. - sendFrames(55, 1000); - checkPausedInstant(55, true); - checkPausedInstant(56, true); + sendFrames(player_1_id, 1000); + checkPausedInstant(player_1_id, true); + checkPausedInstant(player_2_id, true); // Wait for the start delay seconds... AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); // Player 1 should start playing by itself, player 2 should remain paused. - checkPausedInstant(55, false); - checkPausedInstant(56, true); + checkPausedInstant(player_1_id, false); + checkPausedInstant(player_2_id, true); } [Test] public void TestPlayersContinueWhileOthersBuffer() { - start(new[] { 55, 56 }); + start(new[] { player_1_id, player_2_id }); loadSpectateScreen(); // Send initial frames for both players. A few more for player 1. - sendFrames(55, 20); - sendFrames(56, 10); - checkPausedInstant(55, false); - checkPausedInstant(56, false); + sendFrames(player_1_id, 20); + sendFrames(player_2_id, 10); + checkPausedInstant(player_1_id, false); + checkPausedInstant(player_2_id, false); // Eventually player 2 will pause, player 1 must remain running. - checkPaused(56, true); - checkPausedInstant(55, false); + checkPaused(player_2_id, true); + checkPausedInstant(player_1_id, false); // Eventually both players will run out of frames and should pause. - checkPaused(55, true); - checkPausedInstant(56, true); + checkPaused(player_1_id, true); + checkPausedInstant(player_2_id, true); // Send more frames for the first player only. Player 1 should start playing with player 2 remaining paused. - sendFrames(55, 20); - checkPausedInstant(56, true); - checkPausedInstant(55, false); + sendFrames(player_1_id, 20); + checkPausedInstant(player_2_id, true); + checkPausedInstant(player_1_id, false); // Send more frames for the second player. Both should be playing - sendFrames(56, 20); - checkPausedInstant(56, false); - checkPausedInstant(55, false); + sendFrames(player_2_id, 20); + checkPausedInstant(player_2_id, false); + checkPausedInstant(player_1_id, false); } [Test] public void TestPlayersCatchUpAfterFallingBehind() { - start(new[] { 55, 56 }); + start(new[] { player_1_id, player_2_id }); loadSpectateScreen(); // Send initial frames for both players. A few more for player 1. - sendFrames(55, 1000); - sendFrames(56, 10); - checkPausedInstant(55, false); - checkPausedInstant(56, false); + sendFrames(player_1_id, 1000); + sendFrames(player_2_id, 10); + checkPausedInstant(player_1_id, false); + checkPausedInstant(player_2_id, false); // Eventually player 2 will run out of frames and should pause. - checkPaused(56, true); + checkPaused(player_2_id, true); AddWaitStep("wait a few more frames", 10); // Send more frames for player 2. It should unpause. - sendFrames(56, 1000); - checkPausedInstant(56, false); + sendFrames(player_2_id, 1000); + checkPausedInstant(player_2_id, false); // Player 2 should catch up to player 1 after unpausing. - waitForCatchup(56); + waitForCatchup(player_2_id); AddWaitStep("wait a bit", 10); } [Test] public void TestMostInSyncUserIsAudioSource() { - start(new[] { 55, 56 }); + start(new[] { player_1_id, player_2_id }); loadSpectateScreen(); - assertVolume(55, 0); - assertVolume(56, 0); + assertVolume(player_1_id, 0); + assertVolume(player_2_id, 0); - sendFrames(55, 10); - sendFrames(56, 20); - assertVolume(55, 1); - assertVolume(56, 0); + sendFrames(player_1_id, 10); + sendFrames(player_2_id, 20); + assertVolume(player_1_id, 1); + assertVolume(player_2_id, 0); - checkPaused(55, true); - assertVolume(55, 0); - assertVolume(56, 1); + checkPaused(player_1_id, true); + assertVolume(player_1_id, 0); + assertVolume(player_2_id, 1); - sendFrames(55, 100); - waitForCatchup(55); - checkPaused(56, true); - assertVolume(55, 1); - assertVolume(56, 0); + sendFrames(player_1_id, 100); + waitForCatchup(player_1_id); + checkPaused(player_2_id, true); + assertVolume(player_1_id, 1); + assertVolume(player_2_id, 0); - sendFrames(56, 100); - waitForCatchup(56); - assertVolume(55, 1); - assertVolume(56, 0); + sendFrames(player_2_id, 100); + waitForCatchup(player_2_id); + assertVolume(player_1_id, 1); + assertVolume(player_2_id, 0); } private void loadSpectateScreen(bool waitForPlayerLoad = true) From 5b4cb71cc782e4f8ddc2d9e1d6872ef0787f4caf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 17:19:44 +0900 Subject: [PATCH 152/708] Change terminology from "slave" to "player clock" --- .../OnlinePlay/TestSceneCatchUpSyncManager.cs | 104 +++++++++--------- .../Spectate/MultiSpectatorPlayer.cs | 18 +-- .../Spectate/MultiSpectatorScreen.cs | 8 +- .../Multiplayer/Spectate/PlayerArea.cs | 6 +- ...lock.cs => CatchUpSpectatorPlayerClock.cs} | 6 +- .../Spectate/Sync/CatchUpSyncManager.cs | 74 ++++++------- ...SlaveClock.cs => ISpectatorPlayerClock.cs} | 2 +- .../Multiplayer/Spectate/Sync/ISyncManager.cs | 18 +-- 8 files changed, 118 insertions(+), 118 deletions(-) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/{CatchUpSlaveClock.cs => CatchUpSpectatorPlayerClock.cs} (90%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/{ISlaveClock.cs => ISpectatorPlayerClock.cs} (90%) diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index 93801bd5d7..73ec5fb934 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -17,31 +17,31 @@ namespace osu.Game.Tests.OnlinePlay private TestManualClock master; private CatchUpSyncManager syncManager; - private TestSlaveClock slave1; - private TestSlaveClock slave2; + private TestSpectatorPlayerClock player1; + private TestSpectatorPlayerClock player2; [SetUp] public void Setup() { syncManager = new CatchUpSyncManager(master = new TestManualClock()); - syncManager.AddSlave(slave1 = new TestSlaveClock(1)); - syncManager.AddSlave(slave2 = new TestSlaveClock(2)); + syncManager.AddPlayerClock(player1 = new TestSpectatorPlayerClock(1)); + syncManager.AddPlayerClock(player2 = new TestSpectatorPlayerClock(2)); Schedule(() => Child = syncManager); } [Test] - public void TestMasterClockStartsWhenAllSlavesHaveFrames() + public void TestMasterClockStartsWhenAllPlayerClocksHaveFrames() { - setWaiting(() => slave1, false); + setWaiting(() => player1, false); assertMasterState(false); - assertSlaveState(() => slave1, false); - assertSlaveState(() => slave2, false); + assertPlayerClockState(() => player1, false); + assertPlayerClockState(() => player2, false); - setWaiting(() => slave2, false); + setWaiting(() => player2, false); assertMasterState(true); - assertSlaveState(() => slave1, true); - assertSlaveState(() => slave2, true); + assertPlayerClockState(() => player1, true); + assertPlayerClockState(() => player2, true); } [Test] @@ -54,115 +54,115 @@ namespace osu.Game.Tests.OnlinePlay [Test] public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime() { - setWaiting(() => slave1, false); + setWaiting(() => player1, false); AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); assertMasterState(true); } [Test] - public void TestSlaveDoesNotCatchUpWhenSlightlyOutOfSync() + public void TestPlayerClockDoesNotCatchUpWhenSlightlyOutOfSync() { setAllWaiting(false); setMasterTime(CatchUpSyncManager.SYNC_TARGET + 1); - assertCatchingUp(() => slave1, false); + assertCatchingUp(() => player1, false); } [Test] - public void TestSlaveStartsCatchingUpWhenTooFarBehind() + public void TestPlayerClockStartsCatchingUpWhenTooFarBehind() { setAllWaiting(false); setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1); - assertCatchingUp(() => slave1, true); - assertCatchingUp(() => slave2, true); + assertCatchingUp(() => player1, true); + assertCatchingUp(() => player2, true); } [Test] - public void TestSlaveKeepsCatchingUpWhenSlightlyOutOfSync() + public void TestPlayerClockKeepsCatchingUpWhenSlightlyOutOfSync() { setAllWaiting(false); setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1); - setSlaveTime(() => slave1, CatchUpSyncManager.SYNC_TARGET + 1); - assertCatchingUp(() => slave1, true); + setPlayerClockTime(() => player1, CatchUpSyncManager.SYNC_TARGET + 1); + assertCatchingUp(() => player1, true); } [Test] - public void TestSlaveStopsCatchingUpWhenInSync() + public void TestPlayerClockStopsCatchingUpWhenInSync() { setAllWaiting(false); setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 2); - setSlaveTime(() => slave1, CatchUpSyncManager.SYNC_TARGET); - assertCatchingUp(() => slave1, false); - assertCatchingUp(() => slave2, true); + setPlayerClockTime(() => player1, CatchUpSyncManager.SYNC_TARGET); + assertCatchingUp(() => player1, false); + assertCatchingUp(() => player2, true); } [Test] - public void TestSlaveDoesNotStopWhenSlightlyAhead() + public void TestPlayerClockDoesNotStopWhenSlightlyAhead() { setAllWaiting(false); - setSlaveTime(() => slave1, -CatchUpSyncManager.SYNC_TARGET); - assertCatchingUp(() => slave1, false); - assertSlaveState(() => slave1, true); + setPlayerClockTime(() => player1, -CatchUpSyncManager.SYNC_TARGET); + assertCatchingUp(() => player1, false); + assertPlayerClockState(() => player1, true); } [Test] - public void TestSlaveStopsWhenTooFarAheadAndStartsWhenBackInSync() + public void TestPlayerClockStopsWhenTooFarAheadAndStartsWhenBackInSync() { setAllWaiting(false); - setSlaveTime(() => slave1, -CatchUpSyncManager.SYNC_TARGET - 1); + setPlayerClockTime(() => player1, -CatchUpSyncManager.SYNC_TARGET - 1); // This is a silent catchup, where IsCatchingUp = false but IsRunning = false also. - assertCatchingUp(() => slave1, false); - assertSlaveState(() => slave1, false); + assertCatchingUp(() => player1, false); + assertPlayerClockState(() => player1, false); setMasterTime(1); - assertCatchingUp(() => slave1, false); - assertSlaveState(() => slave1, true); + assertCatchingUp(() => player1, false); + assertPlayerClockState(() => player1, true); } [Test] - public void TestInSyncSlaveDoesNotStartIfWaitingOnFrames() + public void TestInSyncPlayerClockDoesNotStartIfWaitingOnFrames() { setAllWaiting(false); - assertSlaveState(() => slave1, true); - setWaiting(() => slave1, true); - assertSlaveState(() => slave1, false); + assertPlayerClockState(() => player1, true); + setWaiting(() => player1, true); + assertPlayerClockState(() => player1, false); } - private void setWaiting(Func slave, bool waiting) - => AddStep($"set slave {slave().Id} waiting = {waiting}", () => slave().WaitingOnFrames.Value = waiting); + private void setWaiting(Func playerClock, bool waiting) + => AddStep($"set player clock {playerClock().Id} waiting = {waiting}", () => playerClock().WaitingOnFrames.Value = waiting); - private void setAllWaiting(bool waiting) => AddStep($"set all slaves waiting = {waiting}", () => + private void setAllWaiting(bool waiting) => AddStep($"set all player clocks waiting = {waiting}", () => { - slave1.WaitingOnFrames.Value = waiting; - slave2.WaitingOnFrames.Value = waiting; + player1.WaitingOnFrames.Value = waiting; + player2.WaitingOnFrames.Value = waiting; }); private void setMasterTime(double time) => AddStep($"set master = {time}", () => master.Seek(time)); /// - /// slave.Time = master.Time - offsetFromMaster + /// clock.Time = master.Time - offsetFromMaster /// - private void setSlaveTime(Func slave, double offsetFromMaster) - => AddStep($"set slave {slave().Id} = master - {offsetFromMaster}", () => slave().Seek(master.CurrentTime - offsetFromMaster)); + private void setPlayerClockTime(Func playerClock, double offsetFromMaster) + => AddStep($"set player clock {playerClock().Id} = master - {offsetFromMaster}", () => playerClock().Seek(master.CurrentTime - offsetFromMaster)); private void assertMasterState(bool running) => AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running); - private void assertCatchingUp(Func slave, bool catchingUp) => - AddAssert($"slave {slave().Id} {(catchingUp ? "is" : "is not")} catching up", () => slave().IsCatchingUp == catchingUp); + private void assertCatchingUp(Func playerClock, bool catchingUp) => + AddAssert($"player clock {playerClock().Id} {(catchingUp ? "is" : "is not")} catching up", () => playerClock().IsCatchingUp == catchingUp); - private void assertSlaveState(Func slave, bool running) - => AddAssert($"slave {slave().Id} {(running ? "is" : "is not")} running", () => slave().IsRunning == running); + private void assertPlayerClockState(Func playerClock, bool running) + => AddAssert($"player clock {playerClock().Id} {(running ? "is" : "is not")} running", () => playerClock().IsRunning == running); - private class TestSlaveClock : TestManualClock, ISlaveClock + private class TestSpectatorPlayerClock : TestManualClock, ISpectatorPlayerClock { public Bindable WaitingOnFrames { get; } = new Bindable(true); @@ -170,7 +170,7 @@ namespace osu.Game.Tests.OnlinePlay public readonly int Id; - public TestSlaveClock(int id) + public TestSpectatorPlayerClock(int id) { Id = id; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 4ed949893c..a17519c3aa 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -19,24 +19,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { private readonly Bindable waitingOnFrames = new Bindable(true); private readonly Score score; - private readonly ISlaveClock slaveClock; + private readonly ISpectatorPlayerClock spectatorPlayerClock; /// /// Creates a new . /// /// The score containing the player's replay. - /// The clock controlling the gameplay running state. - public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISlaveClock slaveClock) + /// The clock controlling the gameplay running state. + public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISpectatorPlayerClock spectatorPlayerClock) : base(score) { this.score = score; - this.slaveClock = slaveClock; + this.spectatorPlayerClock = spectatorPlayerClock; } [BackgroundDependencyLoader] private void load() { - slaveClock.WaitingOnFrames.BindTo(waitingOnFrames); + spectatorPlayerClock.WaitingOnFrames.BindTo(waitingOnFrames); } protected override void UpdateAfterChildren() @@ -48,18 +48,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - => new SlaveGameplayClockContainer(slaveClock); + => new SpectatorGameplayClockContainer(spectatorPlayerClock); - private class SlaveGameplayClockContainer : GameplayClockContainer + private class SpectatorGameplayClockContainer : GameplayClockContainer { - public SlaveGameplayClockContainer([NotNull] IClock sourceClock) + public SpectatorGameplayClockContainer([NotNull] IClock sourceClock) : base(sourceClock) { } protected override void Update() { - // The slave clock's running state is controlled externally, but the local pausing state needs to be updated to stop gameplay. + // The player clock's running state is controlled externally, but the local pausing state needs to be updated to stop gameplay. if (SourceClock.IsRunning) Start(); else diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index eb59d090be..6a8c10e336 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }; for (int i = 0; i < UserIds.Length; i++) - grid.Add(instances[i] = new PlayerArea(UserIds[i], new CatchUpSlaveClock(masterClockContainer.GameplayClock))); + grid.Add(instances[i] = new PlayerArea(UserIds[i], new CatchUpSpectatorPlayerClock(masterClockContainer.GameplayClock))); // Todo: This is not quite correct - it should be per-user to adjust for other mod combinations. var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); @@ -104,7 +104,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (!isCandidateAudioSource(currentAudioSource?.GameplayClock)) { currentAudioSource = instances.Where(i => isCandidateAudioSource(i.GameplayClock)) - .OrderBy(i => Math.Abs(i.GameplayClock.CurrentTime - syncManager.Master.CurrentTime)) + .OrderBy(i => Math.Abs(i.GameplayClock.CurrentTime - syncManager.MasterClock.CurrentTime)) .FirstOrDefault(); foreach (var instance in instances) @@ -112,7 +112,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } } - private bool isCandidateAudioSource([CanBeNull] ISlaveClock clock) + private bool isCandidateAudioSource([CanBeNull] ISpectatorPlayerClock clock) => clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames.Value; protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) @@ -124,7 +124,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate var instance = instances[getIndexForUser(userId)]; instance.LoadScore(gameplayState.Score); - syncManager.AddSlave(instance.GameplayClock); + syncManager.AddPlayerClock(instance.GameplayClock); leaderboard.AddClock(instance.UserId, instance.GameplayClock); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 494f182251..2accfaec0c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -35,10 +35,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public readonly int UserId; /// - /// The used to control the gameplay running state of a loaded . + /// The used to control the gameplay running state of a loaded . /// [NotNull] - public readonly ISlaveClock GameplayClock; + public readonly ISpectatorPlayerClock GameplayClock; /// /// The currently-loaded score. @@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly AudioContainer audioContainer; private OsuScreenStack stack; - public PlayerArea(int userId, [NotNull] ISlaveClock gameplayClock) + public PlayerArea(int userId, [NotNull] ISpectatorPlayerClock gameplayClock) { UserId = userId; GameplayClock = gameplayClock; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSpectatorPlayerClock.cs similarity index 90% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSpectatorPlayerClock.cs index 21241f8158..a8ed7b415f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSpectatorPlayerClock.cs @@ -8,9 +8,9 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { /// - /// A which catches up using rate adjustment. + /// A which catches up using rate adjustment. /// - public class CatchUpSlaveClock : ISlaveClock + public class CatchUpSpectatorPlayerClock : ISpectatorPlayerClock { /// /// The catch up rate. @@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync private readonly IFrameBasedClock masterClock; - public CatchUpSlaveClock(IFrameBasedClock masterClock) + public CatchUpSpectatorPlayerClock(IFrameBasedClock masterClock) { this.masterClock = masterClock; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs index 1d3fcd824c..f5c780e8b1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs @@ -9,46 +9,46 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { /// - /// A which synchronises de-synced slave clocks through catchup. + /// A which synchronises de-synced player clocks through catchup. /// public class CatchUpSyncManager : Component, ISyncManager { /// - /// The offset from the master clock to which slaves should be synchronised to. + /// The offset from the master clock to which player clocks should be synchronised to. /// public const double SYNC_TARGET = 16; /// - /// The offset from the master clock at which slaves begin resynchronising. + /// The offset from the master clock at which player clocks begin resynchronising. /// public const double MAX_SYNC_OFFSET = 50; /// - /// The maximum delay to start gameplay, if any (but not all) slaves are ready. + /// The maximum delay to start gameplay, if any (but not all) player clocks are ready. /// public const double MAXIMUM_START_DELAY = 15000; /// - /// The master clock which is used to control the timing of all slave clocks. + /// The master clock which is used to control the timing of all player clocks clocks. /// - public IAdjustableClock Master { get; } + public IAdjustableClock MasterClock { get; } /// - /// The slave clocks. + /// The player clocks. /// - private readonly List slaves = new List(); + private readonly List playerClocks = new List(); private bool hasStarted; private double? firstStartAttemptTime; public CatchUpSyncManager(IAdjustableClock master) { - Master = master; + MasterClock = master; } - public void AddSlave(ISlaveClock clock) => slaves.Add(clock); + public void AddPlayerClock(ISpectatorPlayerClock clock) => playerClocks.Add(clock); - public void RemoveSlave(ISlaveClock clock) => slaves.Remove(clock); + public void RemovePlayerClock(ISpectatorPlayerClock clock) => playerClocks.Remove(clock); protected override void Update() { @@ -56,9 +56,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync if (!attemptStart()) { - // Ensure all slaves are stopped until the start succeeds. - foreach (var slave in slaves) - slave.Stop(); + // Ensure all player clocks are stopped until the start succeeds. + foreach (var clock in playerClocks) + clock.Stop(); return; } @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync } /// - /// Attempts to start playback. Awaits for all slaves to have available frames for up to milliseconds. + /// Attempts to start playback. Waits for all player clocks to have available frames for up to milliseconds. /// /// Whether playback was started and syncing should occur. private bool attemptStart() @@ -75,14 +75,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync if (hasStarted) return true; - if (slaves.Count == 0) + if (playerClocks.Count == 0) return false; firstStartAttemptTime ??= Time.Current; - int readyCount = slaves.Count(s => !s.WaitingOnFrames.Value); + int readyCount = playerClocks.Count(s => !s.WaitingOnFrames.Value); - if (readyCount == slaves.Count) + if (readyCount == playerClocks.Count) return hasStarted = true; if (readyCount > 0 && (Time.Current - firstStartAttemptTime) > MAXIMUM_START_DELAY) @@ -92,38 +92,38 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync } /// - /// Updates the catchup states of all slave clocks. + /// Updates the catchup states of all player clocks clocks. /// private void updateCatchup() { - for (int i = 0; i < slaves.Count; i++) + for (int i = 0; i < playerClocks.Count; i++) { - var slave = slaves[i]; - double timeDelta = Master.CurrentTime - slave.CurrentTime; + var clock = playerClocks[i]; + double timeDelta = MasterClock.CurrentTime - clock.CurrentTime; - // Check that the slave isn't too far ahead. - // This is a quiet case in which the catchup is done by the master clock, so IsCatchingUp is not set on the slave. + // Check that the player clock isn't too far ahead. + // This is a quiet case in which the catchup is done by the master clock, so IsCatchingUp is not set on the player clock. if (timeDelta < -SYNC_TARGET) { - slave.Stop(); + clock.Stop(); continue; } - // Make sure the slave is running if it can. - if (!slave.WaitingOnFrames.Value) - slave.Start(); + // Make sure the player clock is running if it can. + if (!clock.WaitingOnFrames.Value) + clock.Start(); - if (slave.IsCatchingUp) + if (clock.IsCatchingUp) { - // Stop the slave from catching up if it's within the sync target. + // Stop the player clock from catching up if it's within the sync target. if (timeDelta <= SYNC_TARGET) - slave.IsCatchingUp = false; + clock.IsCatchingUp = false; } else { - // Make the slave start catching up if it's exceeded the maximum allowable sync offset. + // Make the player clock start catching up if it's exceeded the maximum allowable sync offset. if (timeDelta > MAX_SYNC_OFFSET) - slave.IsCatchingUp = true; + clock.IsCatchingUp = true; } } } @@ -133,14 +133,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync /// private void updateMasterClock() { - bool anyInSync = slaves.Any(s => !s.IsCatchingUp); + bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp); - if (Master.IsRunning != anyInSync) + if (MasterClock.IsRunning != anyInSync) { if (anyInSync) - Master.Start(); + MasterClock.Start(); else - Master.Stop(); + MasterClock.Stop(); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorPlayerClock.cs similarity index 90% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorPlayerClock.cs index 6b7a4f5221..46f80288c6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISlaveClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorPlayerClock.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync /// /// A clock which is used by s and managed by an . /// - public interface ISlaveClock : IFrameBasedClock, IAdjustableClock + public interface ISpectatorPlayerClock : IFrameBasedClock, IAdjustableClock { /// /// Whether this clock is waiting on frames to continue playback. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISyncManager.cs index d63ac7e1ca..1b83ea8cf3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISyncManager.cs @@ -6,25 +6,25 @@ using osu.Framework.Timing; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync { /// - /// Manages the synchronisation between one or more s in relation to a master clock. + /// Manages the synchronisation between one or more s in relation to a master clock. /// public interface ISyncManager { /// - /// The master clock which slaves should synchronise to. + /// The master clock which player clocks should synchronise to. /// - IAdjustableClock Master { get; } + IAdjustableClock MasterClock { get; } /// - /// Adds an to manage. + /// Adds an to manage. /// - /// The to add. - void AddSlave(ISlaveClock clock); + /// The to add. + void AddPlayerClock(ISpectatorPlayerClock clock); /// - /// Removes an , stopping it from being managed by this . + /// Removes an , stopping it from being managed by this . /// - /// The to remove. - void RemoveSlave(ISlaveClock clock); + /// The to remove. + void RemovePlayerClock(ISpectatorPlayerClock clock); } } From 120fb8974df0c4a4fcf390e7aaf8ab94d021aa2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 17:22:16 +0900 Subject: [PATCH 153/708] Combine more instances of test player IDs --- .../StatefulMultiplayerClientTest.cs | 4 +- .../Visual/Gameplay/TestSceneSpectator.cs | 3 +- .../TestSceneMultiSpectatorLeaderboard.cs | 42 +++--- .../TestSceneMultiSpectatorScreen.cs | 133 +++++++++--------- .../TestSceneMultiplayerMatchSubScreen.cs | 4 +- .../TestSceneMultiplayerSpectateButton.cs | 10 +- .../Multiplayer/MultiplayerTestScene.cs | 3 + 7 files changed, 100 insertions(+), 99 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 377a33b527..14589f8e6c 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -53,9 +53,9 @@ namespace osu.Game.Tests.NonVisual.Multiplayer Client.RoomSetupAction = room => { room.State = MultiplayerRoomState.Playing; - room.Users.Add(new MultiplayerRoomUser(55) + room.Users.Add(new MultiplayerRoomUser(PLAYER_1_ID) { - User = new User { Id = 55 }, + User = new User { Id = PLAYER_1_ID }, State = MultiplayerUserState.Playing }); }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 74ce66096e..0777571d02 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Users; namespace osu.Game.Tests.Visual.Gameplay @@ -238,7 +239,7 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSpectatorStreamingClient : SpectatorStreamingClient { - public readonly User StreamingUser = new User { Id = 55, Username = "Test user" }; + public readonly User StreamingUser = new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Test user" }; public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index da76adf83f..d3a5daeac6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Multiplayer private readonly Dictionary clocks = new Dictionary { - { 55, new ManualClock() }, - { 56, new ManualClock() } + { PLAYER_1_ID, new ManualClock() }, + { PLAYER_2_ID, new ManualClock() } }; public TestSceneMultiSpectatorLeaderboard() @@ -95,46 +95,46 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("send frames", () => { - // For user 55, send frames in sets of 1. - // For user 56, send frames in sets of 10. + // For player 1, send frames in sets of 1. + // For player 2, send frames in sets of 10. for (int i = 0; i < 100; i++) { - streamingClient.SendFrames(55, i, 1); + streamingClient.SendFrames(PLAYER_1_ID, i, 1); if (i % 10 == 0) - streamingClient.SendFrames(56, i, 10); + streamingClient.SendFrames(PLAYER_2_ID, i, 10); } }); - assertCombo(55, 1); - assertCombo(56, 10); + assertCombo(PLAYER_1_ID, 1); + assertCombo(PLAYER_2_ID, 10); - // Advance to a point where only user 55's frame changes. + // Advance to a point where only user player 1's frame changes. setTime(500); - assertCombo(55, 5); - assertCombo(56, 10); + assertCombo(PLAYER_1_ID, 5); + assertCombo(PLAYER_2_ID, 10); // Advance to a point where both user's frame changes. setTime(1100); - assertCombo(55, 11); - assertCombo(56, 20); + assertCombo(PLAYER_1_ID, 11); + assertCombo(PLAYER_2_ID, 20); - // Advance user 56 only to a point where its frame changes. - setTime(56, 2100); - assertCombo(55, 11); - assertCombo(56, 30); + // Advance user player 2 only to a point where its frame changes. + setTime(PLAYER_2_ID, 2100); + assertCombo(PLAYER_1_ID, 11); + assertCombo(PLAYER_2_ID, 30); // Advance both users beyond their last frame setTime(101 * 100); - assertCombo(55, 100); - assertCombo(56, 100); + assertCombo(PLAYER_1_ID, 100); + assertCombo(PLAYER_2_ID, 100); } [Test] public void TestNoFrames() { - assertCombo(55, 0); - assertCombo(56, 0); + assertCombo(PLAYER_1_ID, 0); + assertCombo(PLAYER_2_ID, 0); } private void setTime(double time) => AddStep($"set time {time}", () => diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 512311ff03..7abeed2cbf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -47,9 +47,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private BeatmapInfo importedBeatmap; private int importedBeatmapId; - private const int player_1_id = 55; - private const int player_2_id = 56; - [BackgroundDependencyLoader] private void load() { @@ -83,29 +80,29 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("start players silently", () => { - Client.CurrentMatchPlayingUserIds.Add(player_1_id); - Client.CurrentMatchPlayingUserIds.Add(player_2_id); - playingUserIds.Add(player_1_id); - playingUserIds.Add(player_2_id); - nextFrame[player_1_id] = 0; - nextFrame[player_2_id] = 0; + Client.CurrentMatchPlayingUserIds.Add(PLAYER_1_ID); + Client.CurrentMatchPlayingUserIds.Add(PLAYER_2_ID); + playingUserIds.Add(PLAYER_1_ID); + playingUserIds.Add(PLAYER_2_ID); + nextFrame[PLAYER_1_ID] = 0; + nextFrame[PLAYER_2_ID] = 0; }); loadSpectateScreen(false); AddWaitStep("wait a bit", 10); - AddStep("load player first_player_id", () => streamingClient.StartPlay(player_1_id, importedBeatmapId)); + AddStep("load player first_player_id", () => streamingClient.StartPlay(PLAYER_1_ID, importedBeatmapId)); AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1); AddWaitStep("wait a bit", 10); - AddStep("load player second_player_id", () => streamingClient.StartPlay(player_2_id, importedBeatmapId)); + AddStep("load player second_player_id", () => streamingClient.StartPlay(PLAYER_2_ID, importedBeatmapId)); AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2); } [Test] public void TestGeneral() { - int[] userIds = Enumerable.Range(0, 4).Select(i => player_1_id + i).ToArray(); + int[] userIds = Enumerable.Range(0, 4).Select(i => PLAYER_1_ID + i).ToArray(); start(userIds); loadSpectateScreen(); @@ -117,123 +114,123 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPlayersMustStartSimultaneously() { - start(new[] { player_1_id, player_2_id }); + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); loadSpectateScreen(); // Send frames for one player only, both should remain paused. - sendFrames(player_1_id, 20); - checkPausedInstant(player_1_id, true); - checkPausedInstant(player_2_id, true); + sendFrames(PLAYER_1_ID, 20); + checkPausedInstant(PLAYER_1_ID, true); + checkPausedInstant(PLAYER_2_ID, true); // Send frames for the other player, both should now start playing. - sendFrames(player_2_id, 20); - checkPausedInstant(player_1_id, false); - checkPausedInstant(player_2_id, false); + sendFrames(PLAYER_2_ID, 20); + checkPausedInstant(PLAYER_1_ID, false); + checkPausedInstant(PLAYER_2_ID, false); } [Test] public void TestPlayersDoNotStartSimultaneouslyIfBufferingForMaximumStartDelay() { - start(new[] { player_1_id, player_2_id }); + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); loadSpectateScreen(); // Send frames for one player only, both should remain paused. - sendFrames(player_1_id, 1000); - checkPausedInstant(player_1_id, true); - checkPausedInstant(player_2_id, true); + sendFrames(PLAYER_1_ID, 1000); + checkPausedInstant(PLAYER_1_ID, true); + checkPausedInstant(PLAYER_2_ID, true); // Wait for the start delay seconds... AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction)); // Player 1 should start playing by itself, player 2 should remain paused. - checkPausedInstant(player_1_id, false); - checkPausedInstant(player_2_id, true); + checkPausedInstant(PLAYER_1_ID, false); + checkPausedInstant(PLAYER_2_ID, true); } [Test] public void TestPlayersContinueWhileOthersBuffer() { - start(new[] { player_1_id, player_2_id }); + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); loadSpectateScreen(); // Send initial frames for both players. A few more for player 1. - sendFrames(player_1_id, 20); - sendFrames(player_2_id, 10); - checkPausedInstant(player_1_id, false); - checkPausedInstant(player_2_id, false); + sendFrames(PLAYER_1_ID, 20); + sendFrames(PLAYER_2_ID, 10); + checkPausedInstant(PLAYER_1_ID, false); + checkPausedInstant(PLAYER_2_ID, false); // Eventually player 2 will pause, player 1 must remain running. - checkPaused(player_2_id, true); - checkPausedInstant(player_1_id, false); + checkPaused(PLAYER_2_ID, true); + checkPausedInstant(PLAYER_1_ID, false); // Eventually both players will run out of frames and should pause. - checkPaused(player_1_id, true); - checkPausedInstant(player_2_id, true); + checkPaused(PLAYER_1_ID, true); + checkPausedInstant(PLAYER_2_ID, true); // Send more frames for the first player only. Player 1 should start playing with player 2 remaining paused. - sendFrames(player_1_id, 20); - checkPausedInstant(player_2_id, true); - checkPausedInstant(player_1_id, false); + sendFrames(PLAYER_1_ID, 20); + checkPausedInstant(PLAYER_2_ID, true); + checkPausedInstant(PLAYER_1_ID, false); // Send more frames for the second player. Both should be playing - sendFrames(player_2_id, 20); - checkPausedInstant(player_2_id, false); - checkPausedInstant(player_1_id, false); + sendFrames(PLAYER_2_ID, 20); + checkPausedInstant(PLAYER_2_ID, false); + checkPausedInstant(PLAYER_1_ID, false); } [Test] public void TestPlayersCatchUpAfterFallingBehind() { - start(new[] { player_1_id, player_2_id }); + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); loadSpectateScreen(); // Send initial frames for both players. A few more for player 1. - sendFrames(player_1_id, 1000); - sendFrames(player_2_id, 10); - checkPausedInstant(player_1_id, false); - checkPausedInstant(player_2_id, false); + sendFrames(PLAYER_1_ID, 1000); + sendFrames(PLAYER_2_ID, 10); + checkPausedInstant(PLAYER_1_ID, false); + checkPausedInstant(PLAYER_2_ID, false); // Eventually player 2 will run out of frames and should pause. - checkPaused(player_2_id, true); + checkPaused(PLAYER_2_ID, true); AddWaitStep("wait a few more frames", 10); // Send more frames for player 2. It should unpause. - sendFrames(player_2_id, 1000); - checkPausedInstant(player_2_id, false); + sendFrames(PLAYER_2_ID, 1000); + checkPausedInstant(PLAYER_2_ID, false); // Player 2 should catch up to player 1 after unpausing. - waitForCatchup(player_2_id); + waitForCatchup(PLAYER_2_ID); AddWaitStep("wait a bit", 10); } [Test] public void TestMostInSyncUserIsAudioSource() { - start(new[] { player_1_id, player_2_id }); + start(new[] { PLAYER_1_ID, PLAYER_2_ID }); loadSpectateScreen(); - assertVolume(player_1_id, 0); - assertVolume(player_2_id, 0); + assertVolume(PLAYER_1_ID, 0); + assertVolume(PLAYER_2_ID, 0); - sendFrames(player_1_id, 10); - sendFrames(player_2_id, 20); - assertVolume(player_1_id, 1); - assertVolume(player_2_id, 0); + sendFrames(PLAYER_1_ID, 10); + sendFrames(PLAYER_2_ID, 20); + assertVolume(PLAYER_1_ID, 1); + assertVolume(PLAYER_2_ID, 0); - checkPaused(player_1_id, true); - assertVolume(player_1_id, 0); - assertVolume(player_2_id, 1); + checkPaused(PLAYER_1_ID, true); + assertVolume(PLAYER_1_ID, 0); + assertVolume(PLAYER_2_ID, 1); - sendFrames(player_1_id, 100); - waitForCatchup(player_1_id); - checkPaused(player_2_id, true); - assertVolume(player_1_id, 1); - assertVolume(player_2_id, 0); + sendFrames(PLAYER_1_ID, 100); + waitForCatchup(PLAYER_1_ID); + checkPaused(PLAYER_2_ID, true); + assertVolume(PLAYER_1_ID, 1); + assertVolume(PLAYER_2_ID, 0); - sendFrames(player_2_id, 100); - waitForCatchup(player_2_id); - assertVolume(player_1_id, 1); - assertVolume(player_2_id, 0); + sendFrames(PLAYER_2_ID, 100); + waitForCatchup(PLAYER_2_ID); + assertVolume(PLAYER_1_ID, 1); + assertVolume(PLAYER_2_ID, 0); } private void loadSpectateScreen(bool waitForPlayerLoad = true) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 7c6c158b5a..f611d5fecf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -119,8 +119,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("join other user (ready)", () => { - Client.AddUser(new User { Id = 55 }); - Client.ChangeUserState(55, MultiplayerUserState.Ready); + Client.AddUser(new User { Id = PLAYER_1_ID }); + Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready); }); AddStep("click spectate button", () => diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index afbc9c32b3..e59b342176 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -157,8 +157,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestReadyButtonEnabledWhenHostAndUsersReady() { - AddStep("add user", () => Client.AddUser(new User { Id = 55 })); - AddStep("set user ready", () => Client.ChangeUserState(55, MultiplayerUserState.Ready)); + AddStep("add user", () => Client.AddUser(new User { Id = PLAYER_1_ID })); + AddStep("set user ready", () => Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready)); addClickSpectateButtonStep(); assertReadyButtonEnablement(true); @@ -169,11 +169,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add user and transfer host", () => { - Client.AddUser(new User { Id = 55 }); - Client.TransferHost(55); + Client.AddUser(new User { Id = PLAYER_1_ID }); + Client.TransferHost(PLAYER_1_ID); }); - AddStep("set user ready", () => Client.ChangeUserState(55, MultiplayerUserState.Ready)); + AddStep("set user ready", () => Client.ChangeUserState(PLAYER_1_ID, MultiplayerUserState.Ready)); addClickSpectateButtonStep(); assertReadyButtonEnablement(false); diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 7775c2bd24..db344b28dd 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -16,6 +16,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { public abstract class MultiplayerTestScene : RoomTestScene { + public const int PLAYER_1_ID = 55; + public const int PLAYER_2_ID = 56; + [Cached(typeof(StatefulMultiplayerClient))] public TestMultiplayerClient Client { get; } From 6626e70c95557a17c07216b5514ae4d9440d92a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 17:30:27 +0900 Subject: [PATCH 154/708] Pass in master clock instead of slave clock --- .../OnlinePlay/TestSceneCatchUpSyncManager.cs | 5 ++++ .../Spectate/MultiSpectatorScreen.cs | 5 +++- .../Multiplayer/Spectate/PlayerArea.cs | 8 ++++--- .../Sync/CatchUpSpectatorPlayerClock.cs | 23 +++++++++++-------- .../Spectate/Sync/ISpectatorPlayerClock.cs | 5 ++++ 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index 73ec5fb934..75ba362146 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -168,6 +168,11 @@ namespace osu.Game.Tests.OnlinePlay public bool IsCatchingUp { get; set; } + public IFrameBasedClock Source + { + set => throw new NotImplementedException(); + } + public readonly int Id; public TestSpectatorPlayerClock(int id) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 6a8c10e336..86c975d12f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -79,7 +79,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }; for (int i = 0; i < UserIds.Length; i++) - grid.Add(instances[i] = new PlayerArea(UserIds[i], new CatchUpSpectatorPlayerClock(masterClockContainer.GameplayClock))); + { + grid.Add(instances[i] = new PlayerArea(UserIds[i], masterClockContainer.GameplayClock)); + syncManager.AddPlayerClock(instances[i].GameplayClock); + } // Todo: This is not quite correct - it should be per-user to adjust for other mod combinations. var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 2accfaec0c..3ef1693ca6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -9,6 +9,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; @@ -38,7 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// The used to control the gameplay running state of a loaded . /// [NotNull] - public readonly ISpectatorPlayerClock GameplayClock; + public readonly ISpectatorPlayerClock GameplayClock = new CatchUpSpectatorPlayerClock(); /// /// The currently-loaded score. @@ -54,10 +55,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly AudioContainer audioContainer; private OsuScreenStack stack; - public PlayerArea(int userId, [NotNull] ISpectatorPlayerClock gameplayClock) + public PlayerArea(int userId, IFrameBasedClock masterClock) { UserId = userId; - GameplayClock = gameplayClock; RelativeSizeAxes = Axes.Both; Masking = true; @@ -71,6 +71,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }, loadingLayer = new LoadingLayer(true) { State = { Value = Visibility.Visible } } }; + + GameplayClock.Source = masterClock; } public void LoadScore([NotNull] Score score) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSpectatorPlayerClock.cs index a8ed7b415f..e2a6b7c9ae 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSpectatorPlayerClock.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 System; using osu.Framework.Bindables; using osu.Framework.Timing; @@ -17,12 +19,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync /// public const double CATCHUP_RATE = 2; - private readonly IFrameBasedClock masterClock; - - public CatchUpSpectatorPlayerClock(IFrameBasedClock masterClock) - { - this.masterClock = masterClock; - } + /// + /// The source clock. + /// + public IFrameBasedClock? Source { get; set; } public double CurrentTime { get; private set; } @@ -52,19 +52,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync public void ProcessFrame() { - masterClock.ProcessFrame(); - ElapsedFrameTime = 0; FramesPerSecond = 0; + if (Source == null) + return; + + Source.ProcessFrame(); + if (IsRunning) { - double elapsedSource = masterClock.ElapsedFrameTime; + double elapsedSource = Source.ElapsedFrameTime; double elapsed = elapsedSource * Rate; CurrentTime += elapsed; ElapsedFrameTime = elapsed; - FramesPerSecond = masterClock.FramesPerSecond; + FramesPerSecond = Source.FramesPerSecond; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorPlayerClock.cs index 46f80288c6..311969c92c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorPlayerClock.cs @@ -20,5 +20,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync /// Whether this clock is resynchronising to the master clock. /// bool IsCatchingUp { get; set; } + + /// + /// The source clock + /// + IFrameBasedClock Source { set; } } } From d7618b63fa38512a7449c65eb6d92bbca140cb54 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 17:35:13 +0900 Subject: [PATCH 155/708] Fix test failure --- .../Multiplayer/Spectate/Sync/CatchUpSyncManager.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs index f5c780e8b1..5912c0803b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs @@ -78,15 +78,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync if (playerClocks.Count == 0) return false; - firstStartAttemptTime ??= Time.Current; - int readyCount = playerClocks.Count(s => !s.WaitingOnFrames.Value); if (readyCount == playerClocks.Count) return hasStarted = true; - if (readyCount > 0 && (Time.Current - firstStartAttemptTime) > MAXIMUM_START_DELAY) - return hasStarted = true; + if (readyCount > 0) + { + firstStartAttemptTime ??= Time.Current; + + if (Time.Current - firstStartAttemptTime > MAXIMUM_START_DELAY) + return hasStarted = true; + } return false; } From 94d0b064931736c7e6947a13b35ec0f39a67889e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 19:01:30 +0900 Subject: [PATCH 156/708] Expose mute adjustment instead --- .../TestSceneMultiSpectatorScreen.cs | 24 +++---- .../Spectate/MultiSpectatorScreen.cs | 2 +- .../Multiplayer/Spectate/PlayerArea.cs | 64 +++++-------------- 3 files changed, 30 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 7abeed2cbf..a4856d1ee8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -209,28 +209,28 @@ namespace osu.Game.Tests.Visual.Multiplayer start(new[] { PLAYER_1_ID, PLAYER_2_ID }); loadSpectateScreen(); - assertVolume(PLAYER_1_ID, 0); - assertVolume(PLAYER_2_ID, 0); + assertMuted(PLAYER_1_ID, true); + assertMuted(PLAYER_2_ID, true); sendFrames(PLAYER_1_ID, 10); sendFrames(PLAYER_2_ID, 20); - assertVolume(PLAYER_1_ID, 1); - assertVolume(PLAYER_2_ID, 0); + assertMuted(PLAYER_1_ID, false); + assertMuted(PLAYER_2_ID, true); checkPaused(PLAYER_1_ID, true); - assertVolume(PLAYER_1_ID, 0); - assertVolume(PLAYER_2_ID, 1); + assertMuted(PLAYER_1_ID, true); + assertMuted(PLAYER_2_ID, false); sendFrames(PLAYER_1_ID, 100); waitForCatchup(PLAYER_1_ID); checkPaused(PLAYER_2_ID, true); - assertVolume(PLAYER_1_ID, 1); - assertVolume(PLAYER_2_ID, 0); + assertMuted(PLAYER_1_ID, false); + assertMuted(PLAYER_2_ID, true); sendFrames(PLAYER_2_ID, 100); waitForCatchup(PLAYER_2_ID); - assertVolume(PLAYER_1_ID, 1); - assertVolume(PLAYER_2_ID, 0); + assertMuted(PLAYER_1_ID, false); + assertMuted(PLAYER_2_ID, true); } private void loadSpectateScreen(bool waitForPlayerLoad = true) @@ -292,8 +292,8 @@ namespace osu.Game.Tests.Visual.Multiplayer private void checkPausedInstant(int userId, bool state) => AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); - private void assertVolume(int userId, double volume) - => AddAssert($"{userId} volume is {volume}", () => getInstance(userId).Volume.Value == volume); + private void assertMuted(int userId, bool muted) + => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); private void waitForCatchup(int userId) => AddUntilStep($"{userId} not catching up", () => !getInstance(userId).GameplayClock.IsCatchingUp); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 86c975d12f..2bd0ad8748 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -111,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate .FirstOrDefault(); foreach (var instance in instances) - instance.Volume.Value = instance == currentAudioSource ? 1 : 0; + instance.Mute = instance != currentAudioSource; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 3ef1693ca6..89c69cc666 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// Provides an area for and manages the hierarchy of a spectated player within a . /// - public class PlayerArea : CompositeDrawable, IAdjustableAudioComponent + public class PlayerArea : CompositeDrawable { /// /// Whether a is loaded in the area. @@ -50,9 +50,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private BeatmapManager beatmapManager { get; set; } + private readonly BindableDouble volumeAdjustment = new BindableDouble(); private readonly Container gameplayContent; private readonly LoadingLayer loadingLayer; - private readonly AudioContainer audioContainer; private OsuScreenStack stack; public PlayerArea(int userId, IFrameBasedClock masterClock) @@ -62,6 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate RelativeSizeAxes = Axes.Both; Masking = true; + AudioContainer audioContainer; InternalChildren = new Drawable[] { audioContainer = new AudioContainer @@ -72,6 +73,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate loadingLayer = new LoadingLayer(true) { State = { Value = Visibility.Visible } } }; + audioContainer.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment); + GameplayClock.Source = masterClock; } @@ -92,55 +95,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate loadingLayer.Hide(); } + private bool mute = true; + + public bool Mute + { + get => mute; + set + { + mute = value; + volumeAdjustment.Value = value ? 0 : 1; + } + } + // Player interferes with global input, so disable input for now. public override bool PropagatePositionalInputSubTree => false; public override bool PropagateNonPositionalInputSubTree => false; - #region IAdjustableAudioComponent - - public IBindable AggregateVolume => audioContainer.AggregateVolume; - - public IBindable AggregateBalance => audioContainer.AggregateBalance; - - public IBindable AggregateFrequency => audioContainer.AggregateFrequency; - - public IBindable AggregateTempo => audioContainer.AggregateTempo; - - public void BindAdjustments(IAggregateAudioAdjustment component) - { - audioContainer.BindAdjustments(component); - } - - public void UnbindAdjustments(IAggregateAudioAdjustment component) - { - audioContainer.UnbindAdjustments(component); - } - - public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) - { - audioContainer.AddAdjustment(type, adjustBindable); - } - - public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) - { - audioContainer.RemoveAdjustment(type, adjustBindable); - } - - public void RemoveAllAdjustments(AdjustableProperty type) - { - audioContainer.RemoveAllAdjustments(type); - } - - public BindableNumber Volume => audioContainer.Volume; - - public BindableNumber Balance => audioContainer.Balance; - - public BindableNumber Frequency => audioContainer.Frequency; - - public BindableNumber Tempo => audioContainer.Tempo; - - #endregion - private class PlayerIsolationContainer : Container { [Cached] From 58ebec48037499b162fb5045c767812a22f82fb5 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Mon, 26 Apr 2021 19:00:40 +0800 Subject: [PATCH 157/708] 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 158/708] 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 7e11d520d587a905f64bf25d023c5515c046b812 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 21:25:34 +0900 Subject: [PATCH 159/708] Remove finished players from multi spectator screen --- .../Spectate/MultiSpectatorScreen.cs | 15 ++++---- osu.Game/Screens/Spectate/SpectatorScreen.cs | 35 ++++++++++++++----- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 2bd0ad8748..c3917e321d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -27,6 +27,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); + private readonly int[] userIds; + [Resolved] private SpectatorStreamingClient spectatorClient { get; set; } @@ -44,7 +46,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public MultiSpectatorScreen(int[] userIds) : base(userIds.Take(PlayerGrid.MAX_PLAYERS).ToArray()) { - instances = new PlayerArea[UserIds.Length]; + this.userIds = GetUserIds().ToArray(); + instances = new PlayerArea[this.userIds.Length]; } [BackgroundDependencyLoader] @@ -78,9 +81,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }) }; - for (int i = 0; i < UserIds.Length; i++) + for (int i = 0; i < userIds.Length; i++) { - grid.Add(instances[i] = new PlayerArea(UserIds[i], masterClockContainer.GameplayClock)); + grid.Add(instances[i] = new PlayerArea(userIds[i], masterClockContainer.GameplayClock)); syncManager.AddPlayerClock(instances[i].GameplayClock); } @@ -89,7 +92,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor(); scoreProcessor.ApplyBeatmap(playableBeatmap); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, UserIds) { Expanded = { Value = true } }, leaderboardContainer.Add); + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, userIds) { Expanded = { Value = true } }, leaderboardContainer.Add); } protected override void LoadComplete() @@ -133,10 +136,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void EndGameplay(int userId) { - spectatorClient.StopWatchingUser(userId); + RemoveUser(userId); leaderboard.RemoveClock(userId); } - private int getIndexForUser(int userId) => Array.IndexOf(UserIds, userId); + private int getIndexForUser(int userId) => Array.IndexOf(userIds, userId); } } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 36768a512d..4aca379ebe 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; +using NuGet.Packaging; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -26,7 +27,8 @@ namespace osu.Game.Screens.Spectate /// public abstract class SpectatorScreen : OsuScreen { - protected readonly int[] UserIds; + protected IEnumerable GetUserIds() => userIds; + private readonly HashSet userIds = new HashSet(); [Resolved] private BeatmapManager beatmaps { get; set; } @@ -54,7 +56,7 @@ namespace osu.Game.Screens.Spectate /// The users to spectate. protected SpectatorScreen(params int[] userIds) { - UserIds = userIds; + this.userIds.AddRange(userIds); } protected override void LoadComplete() @@ -80,20 +82,18 @@ namespace osu.Game.Screens.Spectate private Task populateAllUsers() { - var userLookupTasks = new Task[UserIds.Length]; + var userLookupTasks = new List(); - for (int i = 0; i < UserIds.Length; i++) + foreach (var u in userIds) { - var userId = UserIds[i]; - - userLookupTasks[i] = userLookupCache.GetUserAsync(userId).ContinueWith(task => + userLookupTasks.Add(userLookupCache.GetUserAsync(u).ContinueWith(task => { if (!task.IsCompletedSuccessfully) return; lock (stateLock) - userMap[userId] = task.Result; - }); + userMap[u] = task.Result; + })); } return Task.WhenAll(userLookupTasks); @@ -239,6 +239,23 @@ namespace osu.Game.Screens.Spectate /// The user to end gameplay for. protected abstract void EndGameplay(int userId); + /// + /// Stops spectating a user. + /// + /// The user to stop spectating. + protected void RemoveUser(int userId) + { + lock (stateLock) + { + userFinishedPlaying(userId, null); + + userIds.Remove(userId); + userMap.Remove(userId); + + spectatorClient.StopWatchingUser(userId); + } + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From ed93e26e52b70127ff3e34db5671b25fb6172299 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 21:55:38 +0900 Subject: [PATCH 160/708] Use single method for starting/restarting spectator screen --- .../OnlinePlay/Multiplayer/Multiplayer.cs | 2 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 31 +++++++------------ .../Spectate/MultiSpectatorScreen.cs | 7 +++++ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index ae22e1fcec..085c824bdc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.OnResuming(last); - if (client.Room != null) + if (client.Room != null && client.LocalUser?.State != MultiplayerUserState.Spectating) client.ChangeState(MultiplayerUserState.Idle); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 0cd3b448d2..b8347d0537 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -406,29 +406,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } - private void onRoomUpdated() => Scheduler.Add(() => + private void onRoomUpdated() { - if (client.Room == null) - return; - - Debug.Assert(client.LocalUser != null); - - UpdateMods(); - - if (client.LocalUser.State == MultiplayerUserState.Spectating - && (client.Room.State == MultiplayerRoomState.Playing || client.Room.State == MultiplayerRoomState.WaitingForLoad) - && ParentScreen.IsCurrentScreen()) - { - StartPlay(); - - // If the current user was host, they started the match and the in-progress operation needs to be stopped now. - readyClickOperation?.Dispose(); - readyClickOperation = null; - } - }); + Scheduler.AddOnce(UpdateMods); + } private void onLoadRequested() { + // If the user is spectating, the multi-spectator screen may still be the current screen. + if (!ParentScreen.IsCurrentScreen()) + { + ParentScreen.MakeCurrent(); + + Schedule(onLoadRequested); + return; + } + StartPlay(); readyClickOperation?.Dispose(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index c3917e321d..85ef375fae 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -140,6 +140,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate leaderboard.RemoveClock(userId); } + public override bool OnBackButton() + { + // On a manual exit, set the player state back to idle. + multiplayerClient.ChangeState(MultiplayerUserState.Idle); + return base.OnBackButton(); + } + private int getIndexForUser(int userId) => Array.IndexOf(userIds, userId); } } From 630a6dc46ac262742f9a8a6c1c68e98100c789dc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 22:23:44 +0900 Subject: [PATCH 161/708] Fix missing dependency --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 85ef375fae..ef31a5a7f0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; @@ -32,6 +33,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private SpectatorStreamingClient spectatorClient { get; set; } + [Resolved] + private StatefulMultiplayerClient multiplayerClient { get; set; } + private readonly PlayerArea[] instances; private MasterGameplayClockContainer masterClockContainer; private ISyncManager syncManager; 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 162/708] 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 163/708] 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 164/708] 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 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 165/708] 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 166/708] 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 167/708] 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 168/708] 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 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 169/708] 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 170/708] 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 171/708] 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 172/708] 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 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 173/708] 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 174/708] 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 b75b9a97edfcf27dd2d47dfa95cb34a1a159ae04 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 27 Apr 2021 14:09:58 +0700 Subject: [PATCH 175/708] add OsuMarkdownContainer All of the markdown file in osu-wiki have YAML frontmatter. This YAML is parsed as common markdown paragraph. So we add `UseYamlFrontMatter()` in markdown pipeline builder to parse YAML as `YamlFrontMatterBlock`. --- .../Markdown/OsuMarkdownContainer.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs new file mode 100644 index 0000000000..589c27f0a0 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig; +using Markdig.Extensions.AutoIdentifiers; +using Markdig.Extensions.Yaml; +using Markdig.Syntax; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownContainer : MarkdownContainer + { + protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level) + { + switch (markdownObject) + { + case YamlFrontMatterBlock _: + // Don't parse YAML Frontmatter + break; + + default: + base.AddMarkdownComponent(markdownObject, container, level); + break; + } + } + + protected override MarkdownPipeline CreateBuilder() + => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) + .UseEmojiAndSmiley() + .UseYamlFrontMatter() + .UseAdvancedExtensions().Build(); + } +} From aa07482cbb2f48a193e9afd1dda3ccbea2140170 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 27 Apr 2021 14:15:19 +0700 Subject: [PATCH 176/708] Add OsuMarkdownLinkText Color from https://github.com/ppy/osu-web/blob/d56352aeefc412507c3dab7c16e3e3118421b436/resources/assets/less/functions.less#L159-L165 --- .../Markdown/OsuMarkdownLinkText.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs new file mode 100644 index 0000000000..39b35fd84b --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.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 Markdig.Syntax.Inlines; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownLinkText : MarkdownLinkText + { + private SpriteText spriteText; + + public OsuMarkdownLinkText(string text, LinkInline linkInline) + : base(text, linkInline) + { + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + spriteText.Colour = colourProvider.Light2; + } + + public override SpriteText CreateSpriteText() + { + return spriteText = base.CreateSpriteText(); + } + } +} From c686c5224bdd8050f45c25fcf511dee0ac06f96a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 27 Apr 2021 14:17:51 +0700 Subject: [PATCH 177/708] add OsuMarkdownTextFlowContainer --- .../Markdown/OsuMarkdownTextFlowContainer.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs new file mode 100644 index 0000000000..7a6818b4c2 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.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 Markdig.Syntax.Inlines; +using osu.Framework.Graphics.Containers.Markdown; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownTextFlowContainer : MarkdownTextFlowContainer + { + protected override void AddLinkText(string text, LinkInline linkInline) + => AddDrawable(new OsuMarkdownLinkText(text, linkInline)); + } +} From 6cccbabad83cd5743d51f63c088db7d68ecf3cf5 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 27 Apr 2021 14:19:16 +0700 Subject: [PATCH 178/708] override CreateTextFlow in OsuMarkdownContainer --- osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 589c27f0a0..fc7ced8ea7 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -12,6 +12,8 @@ namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownContainer : MarkdownContainer { + public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer(); + protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level) { switch (markdownObject) From 65aa01866ee8fb6d14ef78ce3873e7bbad4692a8 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 27 Apr 2021 14:38:21 +0700 Subject: [PATCH 179/708] add test scene for OsuMarkdownContainer --- .../TestSceneOsuMarkdownContainer.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs new file mode 100644 index 0000000000..9352404c07 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers.Markdown; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOsuMarkdownContainer : OsuTestScene + { + private OsuMarkdownContainer markdownContainer; + + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange); + + [SetUp] + public void Setup() => Schedule(() => + { + Children = new Drawable[] + { + new Box + { + Colour = overlayColour.Background5, + RelativeSizeAxes = Axes.Both, + }, + new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Child = markdownContainer = new OsuMarkdownContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + }; + }); + + [Test] + public void TestLink() + { + AddStep("Add Link", () => + { + markdownContainer.Text = "[Welcome to osu!](https://osu.ppy.sh)"; + }); + } + } +} From 6959f2a8ccf182d2362553806f5e687a216237cd Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 27 Apr 2021 16:01:32 +0700 Subject: [PATCH 180/708] add OsuMarkdownFencedCodeBlock Reference : https://github.com/ppy/osu-web/blob/d56352aeefc412507c3dab7c16e3e3118421b436/resources/assets/less/bem/osu-md.less#L41-L45 --- .../TestSceneOsuMarkdownContainer.cs | 13 ++++++ .../Markdown/OsuMarkdownContainer.cs | 6 ++- .../Markdown/OsuMarkdownFencedCodeBlock.cs | 44 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 9352404c07..3d782ee184 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -49,5 +49,18 @@ namespace osu.Game.Tests.Visual.UserInterface markdownContainer.Text = "[Welcome to osu!](https://osu.ppy.sh)"; }); } + + [Test] + public void TestFencedCodeBlock() + { + AddStep("Add Code Block", () => + { + markdownContainer.Text = @"```markdown +# Markdown code block + +This is markdown code block. +```"; + }); + } } } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index fc7ced8ea7..95f538eab7 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -12,8 +12,6 @@ namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownContainer : MarkdownContainer { - public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer(); - protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level) { switch (markdownObject) @@ -28,6 +26,10 @@ namespace osu.Game.Graphics.Containers.Markdown } } + public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer(); + + protected override MarkdownFencedCodeBlock CreateFencedCodeBlock(FencedCodeBlock fencedCodeBlock) => new OsuMarkdownFencedCodeBlock(fencedCodeBlock); + protected override MarkdownPipeline CreateBuilder() => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) .UseEmojiAndSmiley() diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs new file mode 100644 index 0000000000..ddd88dd915 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Syntax; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownFencedCodeBlock : MarkdownFencedCodeBlock + { + private Box background; + private MarkdownTextFlowContainer textFlow; + + public OsuMarkdownFencedCodeBlock(FencedCodeBlock fencedCodeBlock) + : base(fencedCodeBlock) + { + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + // TODO : Change to monospace font to match with osu-web + background.Colour = colourProvider.Background6; + textFlow.Colour = colourProvider.Light1; + } + + protected override Drawable CreateBackground() + { + return background = new Box + { + RelativeSizeAxes = Axes.Both, + }; + } + + public override MarkdownTextFlowContainer CreateTextFlow() + { + return textFlow = base.CreateTextFlow(); + } + } +} From f2e56bd3060438b70105e0cdb4a630f43ea31151 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 15:40:35 +0900 Subject: [PATCH 181/708] 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 182/708] 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 183/708] 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 184/708] 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 185/708] 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 186/708] 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 187/708] 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 188/708] 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 189/708] 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 c4d28110d641c9d121d88e4559c783cfc2094218 Mon Sep 17 00:00:00 2001 From: Justus Franklin Tumacder Date: Tue, 27 Apr 2021 19:02:57 +0800 Subject: [PATCH 190/708] 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 191/708] 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 192/708] 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 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 193/708] 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 6a921af08555a0b868cacc04a623f07b69e4211d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 28 Apr 2021 09:23:05 +0700 Subject: [PATCH 194/708] add OsuMarkdownSeparator Reference https://github.com/ppy/osu-web/blob/d56352aeefc412507c3dab7c16e3e3118421b436/resources/assets/less/bem/osu-md.less#L19-L21 --- .../TestSceneOsuMarkdownContainer.cs | 13 ++++++++++ .../Markdown/OsuMarkdownContainer.cs | 2 ++ .../Markdown/OsuMarkdownSeparator.cs | 26 +++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownSeparator.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 3d782ee184..966b5095f9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -62,5 +62,18 @@ This is markdown code block. ```"; }); } + + [Test] + public void TestSeparator() + { + AddStep("Add Seperator", () => + { + markdownContainer.Text = @"Line above + +--- + +Line below"; + }); + } } } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 95f538eab7..6885e3d60b 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -30,6 +30,8 @@ namespace osu.Game.Graphics.Containers.Markdown protected override MarkdownFencedCodeBlock CreateFencedCodeBlock(FencedCodeBlock fencedCodeBlock) => new OsuMarkdownFencedCodeBlock(fencedCodeBlock); + protected override MarkdownSeparator CreateSeparator(ThematicBreakBlock thematicBlock) => new OsuMarkdownSeparator(); + protected override MarkdownPipeline CreateBuilder() => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) .UseEmojiAndSmiley() diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownSeparator.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownSeparator.cs new file mode 100644 index 0000000000..9b28200452 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownSeparator.cs @@ -0,0 +1,26 @@ +// 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.Markdown; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownSeparator : MarkdownSeparator + { + private Drawable separator; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + separator.Colour = colourProvider.Background3; + } + + protected override Drawable CreateSeparator() + { + return separator = base.CreateSeparator(); + } + } +} From aa1cb65eaad96ebdc07d1a5e626284b91957af33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 11:42:10 +0900 Subject: [PATCH 195/708] 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 196/708] 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 197/708] 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 198/708] 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 199/708] 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 200/708] 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 201/708] 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 202/708] 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 736eace00ac68dd56dd86103e7a62a0eb17d13ee Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 28 Apr 2021 10:11:29 +0700 Subject: [PATCH 203/708] add OsuMarkdownQuoteBlock Reference: https://github.com/ppy/osu-web/blob/d56352aeefc412507c3dab7c16e3e3118421b436/resources/assets/less/base.less#L7-L10 --- .../TestSceneOsuMarkdownContainer.cs | 10 +++++ .../Markdown/OsuMarkdownContainer.cs | 2 + .../Markdown/OsuMarkdownQuoteBlock.cs | 45 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 966b5095f9..d78716bede 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -75,5 +75,15 @@ This is markdown code block. Line below"; }); } + + [Test] + public void TestQuote() + { + AddStep("Add quote", () => + { + markdownContainer.Text = + @"> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."; + }); + } } } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 6885e3d60b..9f0000777d 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -32,6 +32,8 @@ namespace osu.Game.Graphics.Containers.Markdown protected override MarkdownSeparator CreateSeparator(ThematicBreakBlock thematicBlock) => new OsuMarkdownSeparator(); + protected override MarkdownQuoteBlock CreateQuoteBlock(QuoteBlock quoteBlock) => new OsuMarkdownQuoteBlock(quoteBlock); + protected override MarkdownPipeline CreateBuilder() => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) .UseEmojiAndSmiley() diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs new file mode 100644 index 0000000000..869cba82f2 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Syntax; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownQuoteBlock : MarkdownQuoteBlock + { + private Drawable background; + + public OsuMarkdownQuoteBlock(QuoteBlock quoteBlock) + : base(quoteBlock) + { + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + background.Colour = colourProvider.Content2; + } + + protected override Drawable CreateBackground() + { + return background = base.CreateBackground(); + } + + public override MarkdownTextFlowContainer CreateTextFlow() + { + var textFlow = base.CreateTextFlow(); + textFlow.Margin = new MarginPadding + { + Top = 10, + Bottom = 10, + Left = 20, + Right = 20, + }; + return textFlow; + } + } +} From 2252d308c8a67e82c81d6b76e116f00cf6f0ea95 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 28 Apr 2021 10:53:00 +0700 Subject: [PATCH 204/708] add OsuMarkdownTableCell Reference : https://github.com/ppy/osu-web/blob/d56352aeefc412507c3dab7c16e3e3118421b436/resources/assets/less/bem/osu-md.less#L254-L277 --- .../Markdown/OsuMarkdownTableCell.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs new file mode 100644 index 0000000000..d8b9145228 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.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 Markdig.Extensions.Tables; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownTableCell : MarkdownTableCell + { + private readonly bool isHeading; + + public OsuMarkdownTableCell(TableCell cell, TableColumnDefinition definition, bool isHeading) + : base(cell, definition) + { + this.isHeading = isHeading; + Masking = false; + BorderThickness = 0; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + var border = new Box + { + RelativeSizeAxes = Axes.X, + }; + + // TODO : Change font weight to 700 for heading + if (isHeading) + { + border.Colour = colourProvider.Background3; + border.Height = 2; + border.Anchor = Anchor.BottomLeft; + border.Origin = Anchor.BottomLeft; + } + else + { + border.Colour = colourProvider.Background4; + border.Height = 1; + border.Anchor = Anchor.TopLeft; + border.Origin = Anchor.TopLeft; + } + + AddInternal(border); + } + } +} From c09067c3d558d5a45dade3efb0aebcbb0d7dcefc Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 28 Apr 2021 10:53:12 +0700 Subject: [PATCH 205/708] add OsuMarkdownTable --- .../TestSceneOsuMarkdownContainer.cs | 14 ++++++++++++++ .../Markdown/OsuMarkdownContainer.cs | 3 +++ .../Containers/Markdown/OsuMarkdownTable.cs | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownTable.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index d78716bede..d80d2362b9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -85,5 +85,19 @@ Line below"; @"> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."; }); } + + [Test] + public void TestTable() + { + AddStep("Add Table", () => + { + markdownContainer.Text = + @"| Left Aligned | Center Aligned | Right Aligned | +| :------------------- | :--------------------: | ---------------------:| +| Long Align Left Text | Long Align Center Text | Long Align Right Text | +| Align Left | Align Center | Align Right | +| Left | Center | Right |"; + }); + } } } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 9f0000777d..56bf72656a 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -3,6 +3,7 @@ using Markdig; using Markdig.Extensions.AutoIdentifiers; +using Markdig.Extensions.Tables; using Markdig.Extensions.Yaml; using Markdig.Syntax; using osu.Framework.Graphics.Containers; @@ -34,6 +35,8 @@ namespace osu.Game.Graphics.Containers.Markdown protected override MarkdownQuoteBlock CreateQuoteBlock(QuoteBlock quoteBlock) => new OsuMarkdownQuoteBlock(quoteBlock); + protected override MarkdownTable CreateTable(Table table) => new OsuMarkdownTable(table); + protected override MarkdownPipeline CreateBuilder() => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) .UseEmojiAndSmiley() diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTable.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTable.cs new file mode 100644 index 0000000000..e0a1ab1220 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTable.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Extensions.Tables; +using osu.Framework.Graphics.Containers.Markdown; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownTable : MarkdownTable + { + public OsuMarkdownTable(Table table) + : base(table) + { + } + + protected override MarkdownTableCell CreateTableCell(TableCell cell, TableColumnDefinition definition, bool isHeading) => new OsuMarkdownTableCell(cell, definition, isHeading); + } +} From 4e691ce4b06fb59a5a953a236a607cae2e53ebf7 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 28 Apr 2021 11:01:54 +0700 Subject: [PATCH 206/708] add test link with inline text markdown --- .../UserInterface/TestSceneOsuMarkdownContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index d80d2362b9..4add9f22a5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -50,6 +50,15 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestLinkWithInlineText() + { + AddStep("Add Link with inline text", () => + { + markdownContainer.Text = "Hey, [welcome to osu!](https://osu.ppy.sh) Please enjoy the game."; + }); + } + [Test] public void TestFencedCodeBlock() { From 4c9e94da2b05b5c5679848258223767b7927b8a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 13:43:16 +0900 Subject: [PATCH 207/708] 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 208/708] 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 48d6c9ac4bee70169ac982611545c9d276126988 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:47:30 +0900 Subject: [PATCH 209/708] 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 210/708] 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 211/708] 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 212/708] 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 213/708] 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 214/708] 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 243605728d5f167680caa5bbb0dee1225960075d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Apr 2021 11:44:05 -0700 Subject: [PATCH 215/708] 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 171e954e89b7e378f599e30d8525850b4aece2ff Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 29 Apr 2021 13:48:00 +0700 Subject: [PATCH 216/708] add unordered list test --- .../TestSceneOsuMarkdownContainer.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 4add9f22a5..11c06abee7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -108,5 +108,25 @@ Line below"; | Left | Center | Right |"; }); } + + [Test] + public void TestUnorderedList() + { + AddStep("Add Unordered List", () => + { + markdownContainer.Text = @"- First item level 1 +- Second item level 1 + - First item level 2 + - First item level 3 + - Second item level 3 + - Third item level 3 + - First item level 4 + - Second item level 4 + - Third item level 4 + - Second item level 2 + - Third item level 2 +- Third item level 1"; + }); + } } } From 9c62c90cfc441a2688afbefb636f430aeea534b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 15:29:25 +0900 Subject: [PATCH 217/708] 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 218/708] 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 219/708] 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 220/708] 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 221/708] 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 222/708] 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 223/708] 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 224/708] 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 225/708] 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 226/708] 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 227/708] 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 228/708] 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 229/708] 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 230/708] 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 231/708] 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 232/708] 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 233/708] 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 234/708] 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 235/708] 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 236/708] 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 237/708] 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 238/708] 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 239/708] 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 240/708] 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 241/708] 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 242/708] 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 243/708] 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 244/708] 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 245/708] 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 820408757a736d3ff3c9dc59cd933bcb1874990a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 09:43:05 +0700 Subject: [PATCH 246/708] add OsuMarkdownListItem --- .../Markdown/OsuMarkdownListItem.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs new file mode 100644 index 0000000000..7b92a2210d --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownListItem : FillFlowContainer + { + public OsuMarkdownListItem() + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + + Direction = FillDirection.Vertical; + Spacing = new Vector2(10, 10); + } + } +} From a24a27940408a6c6ddf082f9073b5b9db79c0eef Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 09:43:21 +0700 Subject: [PATCH 247/708] use OsuMarkdownListItem for ListItemBlock --- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 56bf72656a..c7e9f267bd 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -21,6 +21,13 @@ namespace osu.Game.Graphics.Containers.Markdown // Don't parse YAML Frontmatter break; + case ListItemBlock listItemBlock: + var childContainer = CreateListItem(listItemBlock); + container.Add(childContainer); + foreach (var single in listItemBlock) + base.AddMarkdownComponent(single, childContainer, level); + break; + default: base.AddMarkdownComponent(markdownObject, container, level); break; @@ -37,6 +44,8 @@ namespace osu.Game.Graphics.Containers.Markdown protected override MarkdownTable CreateTable(Table table) => new OsuMarkdownTable(table); + protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock) => new OsuMarkdownListItem(); + protected override MarkdownPipeline CreateBuilder() => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) .UseEmojiAndSmiley() From 1e8b3f3a8c81ab996d4875f9ca55e65bce64fcb4 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 09:47:25 +0700 Subject: [PATCH 248/708] handle list padding in OsuMarkdownListItem Reference : https://github.com/ppy/osu-web/blob/5b0e3ac3ffce6b1aff8c9a8794db56603e885ef8/resources/assets/less/bem/osu-md.less#L193-L194 --- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 6 ++++++ .../Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index c7e9f267bd..0638767414 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -6,6 +6,7 @@ using Markdig.Extensions.AutoIdentifiers; using Markdig.Extensions.Tables; using Markdig.Extensions.Yaml; using Markdig.Syntax; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; @@ -44,6 +45,11 @@ namespace osu.Game.Graphics.Containers.Markdown protected override MarkdownTable CreateTable(Table table) => new OsuMarkdownTable(table); + protected override MarkdownList CreateList(ListBlock listBlock) => new MarkdownList + { + Padding = new MarginPadding(0) + }; + protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock) => new OsuMarkdownListItem(); protected override MarkdownPipeline CreateBuilder() diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index 7b92a2210d..732817a1d5 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -9,6 +9,8 @@ namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownListItem : FillFlowContainer { + private const float default_left_padding = 20; + public OsuMarkdownListItem() { AutoSizeAxes = Axes.Y; @@ -16,6 +18,7 @@ namespace osu.Game.Graphics.Containers.Markdown Direction = FillDirection.Vertical; Spacing = new Vector2(10, 10); + Padding = new MarginPadding { Left = default_left_padding }; } } } From 010c51e6edea430569b1a15455880f9ec086f6af Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 10:12:43 +0700 Subject: [PATCH 249/708] change OsuMarkdownListItem to composite drawable --- .../Markdown/OsuMarkdownContainer.cs | 2 +- .../Containers/Markdown/OsuMarkdownListItem.cs | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 0638767414..fc4eecf297 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Graphics.Containers.Markdown var childContainer = CreateListItem(listItemBlock); container.Add(childContainer); foreach (var single in listItemBlock) - base.AddMarkdownComponent(single, childContainer, level); + base.AddMarkdownComponent(single, childContainer.Content, level); break; default: diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index 732817a1d5..501da7721e 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -7,18 +7,28 @@ using osuTK; namespace osu.Game.Graphics.Containers.Markdown { - public class OsuMarkdownListItem : FillFlowContainer + public class OsuMarkdownListItem : CompositeDrawable { private const float default_left_padding = 20; + public FillFlowContainer Content { get; } + public OsuMarkdownListItem() { AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - - Direction = FillDirection.Vertical; - Spacing = new Vector2(10, 10); Padding = new MarginPadding { Left = default_left_padding }; + + InternalChildren = new Drawable[] + { + Content = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10, 10), + } + }; } } } From e3cc4561abcc0b8a7ec9b0c53e36c3339cb8c6a9 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 10:35:40 +0700 Subject: [PATCH 250/708] add bullet marker in OsuMarkdownListItem --- .../Containers/Markdown/OsuMarkdownListItem.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index 501da7721e..6b35321617 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; using osuTK; namespace osu.Game.Graphics.Containers.Markdown @@ -11,6 +13,9 @@ namespace osu.Game.Graphics.Containers.Markdown { private const float default_left_padding = 20; + [Resolved] + private IMarkdownTextComponent parentTextComponent { get; set; } + public FillFlowContainer Content { get; } public OsuMarkdownListItem() @@ -30,5 +35,18 @@ namespace osu.Game.Graphics.Containers.Markdown } }; } + + [BackgroundDependencyLoader] + private void load() + { + var marker = parentTextComponent.CreateSpriteText(); + marker.Text = "●"; + marker.Font = OsuFont.GetFont(size: marker.Font.Size / 2); + marker.Origin = Anchor.Centre; + marker.X = -default_left_padding / 2; + marker.Y = marker.Font.Size; + + AddInternal(marker); + } } } From f676526cf49c270698499983098ed0eca80c5a2d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 10:39:48 +0700 Subject: [PATCH 251/708] add level in OsuMarkdownListItem --- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 4 ++-- osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index fc4eecf297..1db47d6978 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Graphics.Containers.Markdown break; case ListItemBlock listItemBlock: - var childContainer = CreateListItem(listItemBlock); + var childContainer = CreateListItem(listItemBlock, level); container.Add(childContainer); foreach (var single in listItemBlock) base.AddMarkdownComponent(single, childContainer.Content, level); @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.Containers.Markdown Padding = new MarginPadding(0) }; - protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock) => new OsuMarkdownListItem(); + protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock, int level) => new OsuMarkdownListItem(level); protected override MarkdownPipeline CreateBuilder() => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index 6b35321617..f9800d9262 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -11,6 +11,7 @@ namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownListItem : CompositeDrawable { + private readonly int level; private const float default_left_padding = 20; [Resolved] @@ -18,8 +19,10 @@ namespace osu.Game.Graphics.Containers.Markdown public FillFlowContainer Content { get; } - public OsuMarkdownListItem() + public OsuMarkdownListItem(int level) { + this.level = level; + AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; Padding = new MarginPadding { Left = default_left_padding }; From 781064ba9693c72adda2d84f8a81deba782f9888 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 10:40:06 +0700 Subject: [PATCH 252/708] create list marker based on its level --- .../Containers/Markdown/OsuMarkdownListItem.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index f9800d9262..7b7cb5837c 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -43,7 +43,7 @@ namespace osu.Game.Graphics.Containers.Markdown private void load() { var marker = parentTextComponent.CreateSpriteText(); - marker.Text = "●"; + marker.Text = createTextMarker(); marker.Font = OsuFont.GetFont(size: marker.Font.Size / 2); marker.Origin = Anchor.Centre; marker.X = -default_left_padding / 2; @@ -51,5 +51,20 @@ namespace osu.Game.Graphics.Containers.Markdown AddInternal(marker); } + + private string createTextMarker() + { + switch (level) + { + case 1: + return "●"; + + case 2: + return "○"; + + default: + return "■"; + } + } } } From 1582b0da881a0d7b308ab6730766a8d9457902ee Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 10:43:19 +0700 Subject: [PATCH 253/708] add ordered list test --- .../TestSceneOsuMarkdownContainer.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 11c06abee7..3367fccbf1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -128,5 +128,25 @@ Line below"; - Third item level 1"; }); } + + [Test] + public void TestOrderedList() + { + AddStep("Add Ordered List", () => + { + markdownContainer.Text = @"1. First item level 1 +2. Second item level 1 + 1. First item level 2 + 1. First item level 3 + 2. Second item level 3 + 3. Third item level 3 + 1. First item level 4 + 2. Second item level 4 + 3. Third item level 4 + 2. Second item level 2 + 3. Third item level 2 +3. Third item level 1"; + }); + } } } From 2a3479f30d845c1294fe3214da9fd4938da7f3f4 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 10:47:51 +0700 Subject: [PATCH 254/708] add order in OsuMarkdownListItem for ordered list --- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 2 +- .../Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 1db47d6978..b765378e4c 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.Containers.Markdown Padding = new MarginPadding(0) }; - protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock, int level) => new OsuMarkdownListItem(level); + protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock, int level) => new OsuMarkdownListItem(level, listItemBlock.Order); protected override MarkdownPipeline CreateBuilder() => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index 7b7cb5837c..361e503cd4 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -12,6 +12,8 @@ namespace osu.Game.Graphics.Containers.Markdown public class OsuMarkdownListItem : CompositeDrawable { private readonly int level; + private readonly int order; + private readonly bool isOrdered; private const float default_left_padding = 20; [Resolved] @@ -19,9 +21,11 @@ namespace osu.Game.Graphics.Containers.Markdown public FillFlowContainer Content { get; } - public OsuMarkdownListItem(int level) + public OsuMarkdownListItem(int level, int order) { this.level = level; + this.order = order; + isOrdered = order != 0; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; From c0d9f9f8c687062d444a44edd02916b9a2ef5c2b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 10:48:12 +0700 Subject: [PATCH 255/708] use order number as marker for ordered list --- osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index 361e503cd4..b091e9ac07 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -58,6 +58,11 @@ namespace osu.Game.Graphics.Containers.Markdown private string createTextMarker() { + if (isOrdered) + { + return $"{order}."; + } + switch (level) { case 1: From e6579352f9a3dda275d6fc5f7c313bec11837235 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 10:56:41 +0700 Subject: [PATCH 256/708] add left padding for ordered list In osu-md.less, this rule style[1] removes padding left in ordered list. But in this rule style[2], pseudo element `::before` is used as marker or counter and has minimal width 30px. So we use this as left padding size. [1] https://github.com/ppy/osu-web/blob/5b0e3ac3ffce6b1aff8c9a8794db56603e885ef8/resources/assets/less/bem/osu-md.less#L196-L200 [2] https://github.com/ppy/osu-web/blob/5b0e3ac3ffce6b1aff8c9a8794db56603e885ef8/resources/assets/less/bem/osu-md.less#L210-L219 --- .../Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index b091e9ac07..b448ea154f 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -14,7 +14,9 @@ namespace osu.Game.Graphics.Containers.Markdown private readonly int level; private readonly int order; private readonly bool isOrdered; - private const float default_left_padding = 20; + + private const float ordered_left_padding = 30; + private const float unordered_left_padding = 20; [Resolved] private IMarkdownTextComponent parentTextComponent { get; set; } @@ -29,7 +31,7 @@ namespace osu.Game.Graphics.Containers.Markdown AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - Padding = new MarginPadding { Left = default_left_padding }; + Padding = new MarginPadding { Left = isOrdered ? ordered_left_padding : unordered_left_padding }; InternalChildren = new Drawable[] { From cf53a05dfdfef6eb3c6b9f73cb12c7b20c01a802 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 10:59:23 +0700 Subject: [PATCH 257/708] change marker size and position for ordered list --- .../Containers/Markdown/OsuMarkdownListItem.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index b448ea154f..f020e3e2fc 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -50,10 +50,18 @@ namespace osu.Game.Graphics.Containers.Markdown { var marker = parentTextComponent.CreateSpriteText(); marker.Text = createTextMarker(); - marker.Font = OsuFont.GetFont(size: marker.Font.Size / 2); - marker.Origin = Anchor.Centre; - marker.X = -default_left_padding / 2; - marker.Y = marker.Font.Size; + + if (isOrdered) + { + marker.X = -ordered_left_padding; + } + else + { + marker.Font = OsuFont.GetFont(size: marker.Font.Size / 2); + marker.Origin = Anchor.Centre; + marker.X = -unordered_left_padding / 2; + marker.Y = marker.Font.Size; + } AddInternal(marker); } From cdef07b2eee4cb587fcda722febe81cd9b10366e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 13:09:57 +0900 Subject: [PATCH 258/708] 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 4770a64709e529479c69ad7a4ae024a9801f2fe0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 18:47:22 +0900 Subject: [PATCH 259/708] Add proof of concept components list --- .../TestSceneSkinEditorComponentsList.cs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs new file mode 100644 index 0000000000..4aec91a81f --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Diagnostics; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinEditorComponentsList : SkinnableTestScene + { + [Test] + public void TestToggleEditor() + { + AddStep("show available components", () => + { + SetContents(() => + { + FillFlowContainer fill; + + var scroll = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 0.5f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(20) + } + }; + + var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableComponent).IsAssignableFrom(t)).ToArray(); + + foreach (var type in skinnableTypes) + { + try + { + fill.Add(new OsuSpriteText { Text = type.Name }); + + var instance = (Drawable)Activator.CreateInstance(type); + + Debug.Assert(instance != null); + + instance.Anchor = Anchor.TopCentre; + instance.Origin = Anchor.TopCentre; + + var container = new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Children = new[] + { + instance + } + }; + + switch (instance) + { + case IScoreCounter score: + score.Current.Value = 133773; + break; + + case IComboCounter combo: + combo.Current.Value = 727; + break; + } + + fill.Add(container); + } + catch { } + } + + return scroll; + }); + }); + } + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + } +} From 6442fb819f61dfebd2733cf4033260f8e57b3646 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 12:09:58 +0900 Subject: [PATCH 260/708] Split out component from test scene and fix SongProgress --- .../TestSceneSkinEditorComponentsList.cs | 77 +--------- osu.Game/Screens/Play/SongProgress.cs | 16 ++- .../Skinning/Editor/SkinComponentToolbox.cs | 134 ++++++++++++++++++ 3 files changed, 149 insertions(+), 78 deletions(-) create mode 100644 osu.Game/Skinning/Editor/SkinComponentToolbox.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs index 4aec91a81f..2fd40f5d7c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs @@ -1,18 +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.Diagnostics; -using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Play.HUD; -using osuTK; +using osu.Game.Skinning.Editor; namespace osu.Game.Tests.Visual.Gameplay { @@ -21,71 +14,11 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestToggleEditor() { - AddStep("show available components", () => + AddStep("show available components", () => SetContents(() => new SkinComponentToolbox { - SetContents(() => - { - FillFlowContainer fill; - - var scroll = new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = fill = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 0.5f, - Direction = FillDirection.Vertical, - Spacing = new Vector2(20) - } - }; - - var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableComponent).IsAssignableFrom(t)).ToArray(); - - foreach (var type in skinnableTypes) - { - try - { - fill.Add(new OsuSpriteText { Text = type.Name }); - - var instance = (Drawable)Activator.CreateInstance(type); - - Debug.Assert(instance != null); - - instance.Anchor = Anchor.TopCentre; - instance.Origin = Anchor.TopCentre; - - var container = new Container - { - RelativeSizeAxes = Axes.X, - Height = 100, - Children = new[] - { - instance - } - }; - - switch (instance) - { - case IScoreCounter score: - score.Current.Value = 133773; - break; - - case IComboCounter combo: - combo.Current.Value = 727; - break; - } - - fill.Add(container); - } - catch { } - } - - return scroll; - }); - }); + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + })); } protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index db81633aea..2e94421f36 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -76,10 +76,6 @@ namespace osu.Game.Screens.Play { new SongProgressDisplay { - Masking = true, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, Children = new Drawable[] { info = new SongProgressInfo @@ -187,8 +183,16 @@ namespace osu.Game.Screens.Play public class SongProgressDisplay : Container, ISkinnableComponent { - // TODO: move actual implementation into this. - // exists for skin customisation purposes. + public SongProgressDisplay() + { + // TODO: move actual implementation into this. + // exists for skin customisation purposes. + + Masking = true; + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + } } } } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs new file mode 100644 index 0000000000..5b2b9a8608 --- /dev/null +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -0,0 +1,134 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play.HUD; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Skinning.Editor +{ + public class SkinComponentToolbox : CompositeDrawable + { + public SkinComponentToolbox() + { + RelativeSizeAxes = Axes.Y; + Width = 500; + } + + [BackgroundDependencyLoader] + private void load() + { + FillFlowContainer fill; + + InternalChild = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 0.5f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(20) + } + }; + + var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableComponent).IsAssignableFrom(t)).ToArray(); + + foreach (var type in skinnableTypes) + { + var container = attemptAddComponent(type); + if (container != null) + fill.Add(container); + } + } + + private static Drawable attemptAddComponent(Type type) + { + try + { + var instance = (Drawable)Activator.CreateInstance(type); + + Debug.Assert(instance != null); + + return new ToolboxComponent(instance); + } + catch + { + } + + return null; + } + + private class ToolboxComponent : CompositeDrawable + { + public ToolboxComponent(Drawable instance) + { + Container innerContainer; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OsuSpriteText { Text = instance.GetType().Name }, + innerContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + CornerRadius = 10, + Children = new[] + { + new Box + { + Colour = Color4.Black, + Alpha = 0.5f, + RelativeSizeAxes = Axes.Both, + }, + instance + } + }, + } + }; + + // adjust provided component to fit / display in a known state. + + instance.Anchor = Anchor.Centre; + instance.Origin = Anchor.Centre; + + if (instance.RelativeSizeAxes != Axes.None) + { + innerContainer.AutoSizeAxes = Axes.None; + innerContainer.Height = 100; + } + + switch (instance) + { + case IScoreCounter score: + score.Current.Value = 133773; + break; + + case IComboCounter combo: + combo.Current.Value = 727; + break; + } + } + } + } +} From ae9d1dc40b800708e7f0f904f18acc2c4ecbf757 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 12:35:58 +0900 Subject: [PATCH 261/708] Add component list to main editor interface and enable basic placement --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 2 + .../Skinning/Editor/SkinComponentToolbox.cs | 67 ++++++++++++++----- osu.Game/Skinning/Editor/SkinEditor.cs | 19 ++++++ .../Skinning/Editor/SkinEditorContainer.cs | 9 ++- 4 files changed, 76 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 0c2c6ed454..df481b0558 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -21,6 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("add editor overlay", () => { skinEditor?.Expire(); + Player.ScaleTo(SkinEditorContainer.VISIBLE_TARGET_SCALE); LoadComponentAsync(skinEditor = new SkinEditor(Player), Add); }); } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 5b2b9a8608..f616366336 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -8,6 +8,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; @@ -18,10 +20,12 @@ namespace osu.Game.Skinning.Editor { public class SkinComponentToolbox : CompositeDrawable { + public Action RequestPlacement; + public SkinComponentToolbox() { RelativeSizeAxes = Axes.Y; - Width = 500; + Width = 200; } [BackgroundDependencyLoader] @@ -36,9 +40,6 @@ namespace osu.Game.Skinning.Editor { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 0.5f, Direction = FillDirection.Vertical, Spacing = new Vector2(20) } @@ -48,13 +49,17 @@ namespace osu.Game.Skinning.Editor foreach (var type in skinnableTypes) { - var container = attemptAddComponent(type); - if (container != null) - fill.Add(container); + var component = attemptAddComponent(type); + + if (component != null) + { + component.RequestPlacement = t => RequestPlacement?.Invoke(t); + fill.Add(component); + } } } - private static Drawable attemptAddComponent(Type type) + private static ToolboxComponent attemptAddComponent(Type type) { try { @@ -66,15 +71,20 @@ namespace osu.Game.Skinning.Editor } catch { + return null; } - - return null; } private class ToolboxComponent : CompositeDrawable { - public ToolboxComponent(Drawable instance) + private readonly Drawable component; + private readonly Box box; + + public Action RequestPlacement; + + public ToolboxComponent(Drawable component) { + this.component = component; Container innerContainer; RelativeSizeAxes = Axes.X; @@ -86,7 +96,7 @@ namespace osu.Game.Skinning.Editor AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new OsuSpriteText { Text = instance.GetType().Name }, + new OsuSpriteText { Text = component.GetType().Name }, innerContainer = new Container { RelativeSizeAxes = Axes.X, @@ -95,13 +105,13 @@ namespace osu.Game.Skinning.Editor CornerRadius = 10, Children = new[] { - new Box + box = new Box { Colour = Color4.Black, Alpha = 0.5f, RelativeSizeAxes = Axes.Both, }, - instance + component } }, } @@ -109,16 +119,16 @@ namespace osu.Game.Skinning.Editor // adjust provided component to fit / display in a known state. - instance.Anchor = Anchor.Centre; - instance.Origin = Anchor.Centre; + component.Anchor = Anchor.Centre; + component.Origin = Anchor.Centre; - if (instance.RelativeSizeAxes != Axes.None) + if (component.RelativeSizeAxes != Axes.None) { innerContainer.AutoSizeAxes = Axes.None; innerContainer.Height = 100; } - switch (instance) + switch (component) { case IScoreCounter score: score.Current.Value = 133773; @@ -129,6 +139,27 @@ namespace osu.Game.Skinning.Editor break; } } + + [Resolved] + private OsuColour colours { get; set; } + + protected override bool OnClick(ClickEvent e) + { + RequestPlacement?.Invoke(component.GetType()); + return true; + } + + protected override bool OnHover(HoverEvent e) + { + box.FadeColour(colours.Yellow, 100); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + box.FadeColour(Color4.Black, 100); + base.OnHoverLost(e); + } } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 562dd23224..885d41043c 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -1,13 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Screens.Play; namespace osu.Game.Skinning.Editor { @@ -45,6 +49,12 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.X }, new SkinBlueprintContainer(target), + new SkinComponentToolbox + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RequestPlacement = placeComponent + } } }; @@ -56,6 +66,15 @@ namespace osu.Game.Skinning.Editor }); } + private void placeComponent(Type type) + { + var instance = (Drawable)Activator.CreateInstance(type); + + var targetContainer = target.ChildrenOfType().FirstOrDefault(); + + targetContainer?.Add(instance); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Skinning/Editor/SkinEditorContainer.cs b/osu.Game/Skinning/Editor/SkinEditorContainer.cs index adb1abefd1..06bf278010 100644 --- a/osu.Game/Skinning/Editor/SkinEditorContainer.cs +++ b/osu.Game/Skinning/Editor/SkinEditorContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning.Editor private readonly ScalingContainer target; private SkinEditor skinEditor; - private const float visible_target_scale = 0.8f; + public const float VISIBLE_TARGET_SCALE = 0.8f; [Resolved] private OsuColour colours { get; set; } @@ -63,12 +63,14 @@ namespace osu.Game.Skinning.Editor { 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; + target.RelativePositionAxes = Axes.Both; + + target.ScaleTo(VISIBLE_TARGET_SCALE, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); + target.MoveToX(0.1f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); } else { @@ -76,6 +78,7 @@ namespace osu.Game.Skinning.Editor target.AllowScaling = true; target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => target.Masking = false); + target.MoveToX(0f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); } } From 5585a7d4380cb6a7070f7f8dbaa6a9fe3c0caaad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 12:41:18 +0900 Subject: [PATCH 262/708] Add basic interfaces for skinnable target containers --- .../Screens/Play/HUD/IDefaultSkinnableTarget.cs | 12 ++++++++++++ osu.Game/Screens/Play/HUD/ISkinnableTarget.cs | 15 +++++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Skinning/Editor/SkinEditor.cs | 4 ++-- 4 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/IDefaultSkinnableTarget.cs create mode 100644 osu.Game/Screens/Play/HUD/ISkinnableTarget.cs diff --git a/osu.Game/Screens/Play/HUD/IDefaultSkinnableTarget.cs b/osu.Game/Screens/Play/HUD/IDefaultSkinnableTarget.cs new file mode 100644 index 0000000000..208d920dec --- /dev/null +++ b/osu.Game/Screens/Play/HUD/IDefaultSkinnableTarget.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. + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// The default placement location for new s. + /// + public interface IDefaultSkinnableTarget : ISkinnableTarget + { + } +} diff --git a/osu.Game/Screens/Play/HUD/ISkinnableTarget.cs b/osu.Game/Screens/Play/HUD/ISkinnableTarget.cs new file mode 100644 index 0000000000..61a173ed02 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ISkinnableTarget.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// Denotes a container which can house s. + /// + public interface ISkinnableTarget : IContainerCollection + { + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 669c920017..eb6e599a19 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -22,7 +22,7 @@ using osuTK; namespace osu.Game.Screens.Play { [Cached] - public class HUDOverlay : Container, IKeyBindingHandler + public class HUDOverlay : Container, IKeyBindingHandler, IDefaultSkinnableTarget { public const float FADE_DURATION = 300; diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 885d41043c..6d189dc027 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -11,7 +11,7 @@ using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; -using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning.Editor { @@ -70,7 +70,7 @@ namespace osu.Game.Skinning.Editor { var instance = (Drawable)Activator.CreateInstance(type); - var targetContainer = target.ChildrenOfType().FirstOrDefault(); + var targetContainer = target.ChildrenOfType().FirstOrDefault(); targetContainer?.Add(instance); } From 8b82a07914cfb531f01f2c75c8d0b27da83eb5c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 12:42:32 +0900 Subject: [PATCH 263/708] Move skin-related interfaces out of HUD namespace --- osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs | 1 + osu.Game/Screens/Play/HUD/DefaultComboCounter.cs | 1 + osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs | 1 + osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs | 1 + osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 1 + osu.Game/Screens/Play/HUDOverlay.cs | 1 + osu.Game/Screens/Play/SongProgress.cs | 2 +- osu.Game/Skinning/Editor/SkinBlueprint.cs | 1 - osu.Game/Skinning/Editor/SkinBlueprintContainer.cs | 1 - osu.Game/Skinning/Editor/SkinEditor.cs | 1 - osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 1 - .../{Screens/Play/HUD => Skinning}/IDefaultSkinnableTarget.cs | 2 +- osu.Game/{Screens/Play/HUD => Skinning}/ISkinnableComponent.cs | 2 +- osu.Game/{Screens/Play/HUD => Skinning}/ISkinnableTarget.cs | 2 +- osu.Game/Skinning/LegacyScoreCounter.cs | 1 - 15 files changed, 10 insertions(+), 9 deletions(-) rename osu.Game/{Screens/Play/HUD => Skinning}/IDefaultSkinnableTarget.cs (90%) rename osu.Game/{Screens/Play/HUD => Skinning}/ISkinnableComponent.cs (91%) rename osu.Game/{Screens/Play/HUD => Skinning}/ISkinnableTarget.cs (92%) diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index b8a43708b4..5db1e96dc9 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Play.HUD diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 959766ecd1..a6bd95b36c 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Play.HUD diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index e3cd71691d..f99428d07e 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -13,6 +13,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; +using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index dde5c18b38..1f154fe168 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 3b24c8cc9e..26d5e622f7 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index eb6e599a19..621d604278 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Play diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 2e94421f36..d85f3538e4 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -14,7 +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; +using osu.Game.Skinning; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index 491a403325..b82b861e3e 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -10,7 +10,6 @@ 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 diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index f2bc8ecddf..9c0327df00 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -6,7 +6,6 @@ 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 { diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 6d189dc027..902cb389e6 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -11,7 +11,6 @@ using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; -using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning.Editor { diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 0408ce74a6..e95bd5f8a3 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -10,7 +10,6 @@ using osu.Game.Extensions; 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 diff --git a/osu.Game/Screens/Play/HUD/IDefaultSkinnableTarget.cs b/osu.Game/Skinning/IDefaultSkinnableTarget.cs similarity index 90% rename from osu.Game/Screens/Play/HUD/IDefaultSkinnableTarget.cs rename to osu.Game/Skinning/IDefaultSkinnableTarget.cs index 208d920dec..24fb454af8 100644 --- a/osu.Game/Screens/Play/HUD/IDefaultSkinnableTarget.cs +++ b/osu.Game/Skinning/IDefaultSkinnableTarget.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.Screens.Play.HUD +namespace osu.Game.Skinning { /// /// The default placement location for new s. diff --git a/osu.Game/Screens/Play/HUD/ISkinnableComponent.cs b/osu.Game/Skinning/ISkinnableComponent.cs similarity index 91% rename from osu.Game/Screens/Play/HUD/ISkinnableComponent.cs rename to osu.Game/Skinning/ISkinnableComponent.cs index 6d4558443f..f6b0a182b4 100644 --- a/osu.Game/Screens/Play/HUD/ISkinnableComponent.cs +++ b/osu.Game/Skinning/ISkinnableComponent.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Skinning { /// /// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications. diff --git a/osu.Game/Screens/Play/HUD/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs similarity index 92% rename from osu.Game/Screens/Play/HUD/ISkinnableTarget.cs rename to osu.Game/Skinning/ISkinnableTarget.cs index 61a173ed02..607e89fdec 100644 --- a/osu.Game/Screens/Play/HUD/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Skinning { /// /// Denotes a container which can house s. diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index cae8044242..c270ce7e69 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -5,7 +5,6 @@ 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 From 20ff05c9ffacd5ebc129c1ffe4239a65c3a02bc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 13:03:54 +0900 Subject: [PATCH 264/708] Fix crosstalk between notification/setting overlay nudge padding and skin overlay position adjust --- osu.Game/OsuGame.cs | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 77fb9c3b63..b7824a01a4 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -595,28 +595,35 @@ namespace osu.Game ActionRequested = action => volume.Adjust(action), ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), }, - screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) + screenOffsetContainer = new Container { RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Children = new Drawable[] { - receptor = new BackButton.Receptor(), - ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, - BackButton = new BackButton(receptor) + screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Action = () => + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] { - var currentScreen = ScreenStack.CurrentScreen as IOsuScreen; + receptor = new BackButton.Receptor(), + ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, + BackButton = new BackButton(receptor) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = () => + { + var currentScreen = ScreenStack.CurrentScreen as IOsuScreen; - if (currentScreen?.AllowBackButton == true && !currentScreen.OnBackButton()) - ScreenStack.Exit(); + if (currentScreen?.AllowBackButton == true && !currentScreen.OnBackButton()) + ScreenStack.Exit(); + } + }, + logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, - logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, overlayContent = new Container { RelativeSizeAxes = Axes.Both }, @@ -766,7 +773,7 @@ namespace osu.Game if (notifications.State.Value == Visibility.Visible) offset -= Toolbar.HEIGHT / 2; - screenContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint); + screenOffsetContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint); } Settings.State.ValueChanged += _ => updateScreenOffset(); @@ -946,6 +953,8 @@ namespace osu.Game private ScalingContainer screenContainer; + private Container screenOffsetContainer; + private SkinEditorContainer skinEditor; protected override bool OnExiting() @@ -966,7 +975,7 @@ namespace osu.Game { base.UpdateAfterChildren(); - screenContainer.Padding = new MarginPadding { Top = ToolbarOffset }; + screenOffsetContainer.Padding = new MarginPadding { Top = ToolbarOffset }; overlayContent.Padding = new MarginPadding { Top = ToolbarOffset }; MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; From bde72faa7ccf81f74b9fbd16538f954ce9488edf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 13:04:10 +0900 Subject: [PATCH 265/708] Limit components list height to better align with actual viewport --- osu.Game/Skinning/Editor/SkinEditor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 902cb389e6..298976c39b 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -52,6 +52,7 @@ namespace osu.Game.Skinning.Editor { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Height = SkinEditorContainer.VISIBLE_TARGET_SCALE, RequestPlacement = placeComponent } } From 3681db491caa5e03210c159682f3b3a921569447 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 11:21:20 +0700 Subject: [PATCH 266/708] add long mixed list test Copied from https://github.com/ppy/osu-wiki/blob/master/wiki/Tournaments/OWC/2020/en.md#tournament-rules --- .../TestSceneOsuMarkdownContainer.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 3367fccbf1..852a2e32e2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -148,5 +148,44 @@ Line below"; 3. Third item level 1"; }); } + + [Test] + public void TestLongMixedList() + { + AddStep("Add long mixed list", () => + { + markdownContainer.Text = @"1. The osu! World Cup is a country-based team tournament played on the osu! game mode. + - While this competition is planned as a 4 versus 4 setup, this may change depending on the number of incoming registrations. +2. Beatmap scoring is based on Score V2. +3. The beatmaps for each round will be announced by the map selectors in advance on the Sunday before the actual matches take place. Only these beatmaps will be used during the respective matches. + - One beatmap will be a tiebreaker beatmap. This beatmap will only be played in case of a tie. **The only exception to this is the Qualifiers pool.** +4. The match schedule will be settled by the Tournament Management (see the [scheduling instructions](#scheduling-instructions)). +5. If no staff or referee is available, the match will be postponed. +6. Use of the Visual Settings to alter background dim or disable beatmap elements like storyboards and skins are allowed. +7. If the beatmap ends in a draw, the map will be nullified and replayed. +8. If a player disconnects, their scores will not be counted towards their team's total. + - Disconnects within 30 seconds or 25% of the beatmap length (whichever happens first) after beatmap begin can be aborted and/or rematched. This is up to the referee's discretion. +9. Beatmaps cannot be reused in the same match unless the map was nullified. +10. If less than the minimum required players attend, the maximum time the match can be postponed is 10 minutes. +11. Exchanging players during a match is allowed without limitations. + - **If a map rematch is required, exchanging players is not allowed. With the referee's discretion, an exception can be made if the previous roster is unavailable to play.** +12. Lag is not a valid reason to nullify a beatmap. +13. All players are supposed to keep the match running fluently and without delays. Penalties can be issued to the players if they cause excessive match delays. +14. If a player disconnects between maps and the team cannot provide a replacement, the match can be delayed 10 minutes at maximum. +15. All players and referees must be treated with respect. Instructions of the referees and tournament Management are to be followed. Decisions labeled as final are not to be objected. +16. Disrupting the match by foul play, insulting and provoking other players or referees, delaying the match or other deliberate inappropriate misbehavior is strictly prohibited. +17. The multiplayer chatrooms are subject to the [osu! community rules](/wiki/Rules). + - Breaking the chat rules will result in a silence. Silenced players can not participate in multiplayer matches and must be exchanged for the time being. +18. **The seeding method will be revealed after all the teams have played their Qualifier rounds.** +19. Unexpected incidents are handled by the tournament management. Referees may allow higher tolerance depending on the circumstances. This is up to their discretion. +20. Penalties for violating the tournament rules may include: + - Exclusion of specific players for one beatmap + - Exclusion of specific players for an entire match + - Declaring the match as Lost by Default + - Disqualification from the entire tournament + - Disqualification from the current and future official tournaments until appealed + - Any modification of these rules will be announced."; + }); + } } } From 11d0f12455b1534efbd8edaef2c810e4dd61f2a2 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 11:36:51 +0700 Subject: [PATCH 267/708] change create text marker to virtual --- osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index f020e3e2fc..dce032d626 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -49,7 +49,7 @@ namespace osu.Game.Graphics.Containers.Markdown private void load() { var marker = parentTextComponent.CreateSpriteText(); - marker.Text = createTextMarker(); + marker.Text = CreateTextMarker(); if (isOrdered) { @@ -66,7 +66,7 @@ namespace osu.Game.Graphics.Containers.Markdown AddInternal(marker); } - private string createTextMarker() + protected virtual string CreateTextMarker() { if (isOrdered) { From fc4fa4f696c3c8fcf168a819a902454a89232b36 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 11:48:37 +0700 Subject: [PATCH 268/708] use ListBlock IsOrdered to determine ordered or unordered list --- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 2 +- .../Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index b765378e4c..02e674aaec 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.Containers.Markdown Padding = new MarginPadding(0) }; - protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock, int level) => new OsuMarkdownListItem(level, listItemBlock.Order); + protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock, int level) => new OsuMarkdownListItem(listItemBlock, level); protected override MarkdownPipeline CreateBuilder() => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index dce032d626..98b1fd1381 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.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 Markdig.Syntax; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,11 +24,11 @@ namespace osu.Game.Graphics.Containers.Markdown public FillFlowContainer Content { get; } - public OsuMarkdownListItem(int level, int order) + public OsuMarkdownListItem(ListItemBlock listItemBlock, int level) { this.level = level; - this.order = order; - isOrdered = order != 0; + this.order = listItemBlock.Order; + isOrdered = ((ListBlock)listItemBlock.Parent).IsOrdered; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; From a1e64f4e3cc9faae55d3891d6850e1fb17d0767f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 14:37:49 +0900 Subject: [PATCH 269/708] Use the existing toolbox design --- .../TestSceneSkinEditorComponentsList.cs | 2 +- .../Rulesets/Edit/ScrollingToolboxGroup.cs | 32 +++++++++++++++++++ .../Skinning/Editor/SkinComponentToolbox.cs | 23 ++++++------- osu.Game/Skinning/Editor/SkinEditor.cs | 3 +- 4 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs index 2fd40f5d7c..14bd62b98a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestToggleEditor() { - AddStep("show available components", () => SetContents(() => new SkinComponentToolbox + AddStep("show available components", () => SetContents(() => new SkinComponentToolbox(300) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs b/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.cs new file mode 100644 index 0000000000..a54f574bff --- /dev/null +++ b/osu.Game/Rulesets/Edit/ScrollingToolboxGroup.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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Rulesets.Edit +{ + public class ScrollingToolboxGroup : ToolboxGroup + { + protected readonly OsuScrollContainer Scroll; + + protected override Container Content { get; } + + public ScrollingToolboxGroup(string title, float scrollAreaHeight) + : base(title) + { + base.Content.Add(Scroll = new OsuScrollContainer + { + RelativeSizeAxes = Axes.X, + Height = scrollAreaHeight, + Child = Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + }); + } + } +} diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index f616366336..6b7439c229 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -10,21 +10,22 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Edit; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning.Editor { - public class SkinComponentToolbox : CompositeDrawable + public class SkinComponentToolbox : ScrollingToolboxGroup { public Action RequestPlacement; - public SkinComponentToolbox() + public SkinComponentToolbox(float height) + : base("Components", height) { - RelativeSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.None; Width = 200; } @@ -33,16 +34,12 @@ namespace osu.Game.Skinning.Editor { FillFlowContainer fill; - InternalChild = new OsuScrollContainer + Child = fill = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Child = fill = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(20) - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(20) }; var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableComponent).IsAssignableFrom(t)).ToArray(); diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 298976c39b..18a8b220df 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -48,11 +48,10 @@ namespace osu.Game.Skinning.Editor RelativeSizeAxes = Axes.X }, new SkinBlueprintContainer(target), - new SkinComponentToolbox + new SkinComponentToolbox(600) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Height = SkinEditorContainer.VISIBLE_TARGET_SCALE, RequestPlacement = placeComponent } } From e4f895b49046948816dc3f260ba399aad816f153 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 14:48:37 +0900 Subject: [PATCH 270/708] 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 e663629bc64ceedb95fda5adbf26c2a6c1be99cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 15:22:51 +0900 Subject: [PATCH 271/708] Match button appearance to that of the beatmap editor --- .../Skinning/Editor/SkinComponentToolbox.cs | 107 +++++++++--------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 6b7439c229..46b8d5d5ea 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -5,12 +5,14 @@ using System; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Effects; 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 osu.Game.Screens.Play.HUD; using osuTK; @@ -22,6 +24,8 @@ namespace osu.Game.Skinning.Editor { public Action RequestPlacement; + private const float component_display_scale = 0.8f; + public SkinComponentToolbox(float height) : base("Components", height) { @@ -56,7 +60,7 @@ namespace osu.Game.Skinning.Editor } } - private static ToolboxComponent attemptAddComponent(Type type) + private static ToolboxComponentButton attemptAddComponent(Type type) { try { @@ -64,7 +68,7 @@ namespace osu.Game.Skinning.Editor Debug.Assert(instance != null); - return new ToolboxComponent(instance); + return new ToolboxComponentButton(instance); } catch { @@ -72,59 +76,60 @@ namespace osu.Game.Skinning.Editor } } - private class ToolboxComponent : CompositeDrawable + private class ToolboxComponentButton : OsuButton { private readonly Drawable component; - private readonly Box box; public Action RequestPlacement; - public ToolboxComponent(Drawable component) + private Container innerContainer; + + public ToolboxComponentButton(Drawable component) { this.component = component; - Container innerContainer; + + Enabled.Value = true; RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; + Height = 70; + } - InternalChild = new FillFlowContainer + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Gray3; + Content.EdgeEffect = new EdgeEffectParameters { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new OsuSpriteText { Text = component.GetType().Name }, - innerContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Masking = true, - CornerRadius = 10, - Children = new[] - { - box = new Box - { - Colour = Color4.Black, - Alpha = 0.5f, - RelativeSizeAxes = Axes.Both, - }, - component - } - }, - } + Type = EdgeEffectType.Shadow, + Radius = 2, + Offset = new Vector2(0, 1), + Colour = Color4.Black.Opacity(0.5f) }; - // adjust provided component to fit / display in a known state. + AddRange(new Drawable[] + { + new OsuSpriteText + { + Text = component.GetType().Name, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + innerContainer = new Container + { + Y = 10, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(component_display_scale), + Masking = true, + Child = component + } + }); + // adjust provided component to fit / display in a known state. component.Anchor = Anchor.Centre; component.Origin = Anchor.Centre; - if (component.RelativeSizeAxes != Axes.None) - { - innerContainer.AutoSizeAxes = Axes.None; - innerContainer.Height = 100; - } - switch (component) { case IScoreCounter score: @@ -137,26 +142,22 @@ namespace osu.Game.Skinning.Editor } } - [Resolved] - private OsuColour colours { get; set; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (component.RelativeSizeAxes != Axes.None) + { + innerContainer.AutoSizeAxes = Axes.None; + innerContainer.Height = 100; + } + } protected override bool OnClick(ClickEvent e) { RequestPlacement?.Invoke(component.GetType()); return true; } - - protected override bool OnHover(HoverEvent e) - { - box.FadeColour(colours.Yellow, 100); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - box.FadeColour(Color4.Black, 100); - base.OnHoverLost(e); - } } } } From 786ab163f622c0770c2b63a27e54db958881f499 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 30 Apr 2021 12:40:16 -0700 Subject: [PATCH 272/708] 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 273/708] 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 274/708] 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 275/708] 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 276/708] 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 277/708] 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 278/708] 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 279/708] 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 280/708] 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 281/708] 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 282/708] 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 88aaa9b3320124203abbbef8169b82133956e5dd Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 2 May 2021 22:35:30 +0700 Subject: [PATCH 283/708] add inline code colour Reference : https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/bem/osu-md.less#L12-L17 --- .../UserInterface/TestSceneOsuMarkdownContainer.cs | 9 +++++++++ .../Containers/Markdown/OsuMarkdownTextFlowContainer.cs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 852a2e32e2..99bbc22125 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -59,6 +59,15 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestInlineCode() + { + AddStep("Add inline code", () => + { + markdownContainer.Text = "This is `inline code` text"; + }); + } + [Test] public void TestFencedCodeBlock() { diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 7a6818b4c2..535e1fb9a0 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -2,13 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using Markdig.Syntax.Inlines; +using osu.Framework.Allocation; using osu.Framework.Graphics.Containers.Markdown; +using osu.Game.Overlays; namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownTextFlowContainer : MarkdownTextFlowContainer { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + protected override void AddLinkText(string text, LinkInline linkInline) => AddDrawable(new OsuMarkdownLinkText(text, linkInline)); + + // TODO : Add background (colour B6) and change font to monospace + protected override void AddCodeInLine(CodeInline codeInline) + => AddText(codeInline.Content, t => { t.Colour = colourProvider.Light1; }); } } From 18bfcd7b222d9e13d9b6c07fdd20fed2aa30d8d0 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 2 May 2021 22:41:11 +0700 Subject: [PATCH 284/708] add hover colour to OsuMarkdownLinkText --- .../Containers/Markdown/OsuMarkdownLinkText.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs index 39b35fd84b..2efb60d125 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs @@ -5,12 +5,16 @@ using Markdig.Syntax.Inlines; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Overlays; namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownLinkText : MarkdownLinkText { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + private SpriteText spriteText; public OsuMarkdownLinkText(string text, LinkInline linkInline) @@ -19,7 +23,7 @@ namespace osu.Game.Graphics.Containers.Markdown } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load() { spriteText.Colour = colourProvider.Light2; } @@ -28,5 +32,17 @@ namespace osu.Game.Graphics.Containers.Markdown { return spriteText = base.CreateSpriteText(); } + + protected override bool OnHover(HoverEvent e) + { + spriteText.Colour = colourProvider.Light1; + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + spriteText.Colour = colourProvider.Light2; + base.OnHoverLost(e); + } } } From 59cb5f4679aab9b861fa3ca0b2c6ed473b22d330 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Apr 2021 09:26:43 -0700 Subject: [PATCH 285/708] 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 286/708] 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 287/708] 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 288/708] 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 289/708] 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 8a2926c0b54e41f72cc1ba9c7110655dac392da6 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 3 May 2021 09:18:45 +0700 Subject: [PATCH 290/708] change default font size to 14 Reference : - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/bem/osu-md.less#L9 - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/variables.less#L161 --- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 7 +++++++ .../Containers/Markdown/OsuMarkdownTextFlowContainer.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 02e674aaec..c4190d8c4f 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -9,6 +9,8 @@ using Markdig.Syntax; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.Containers.Markdown { @@ -35,6 +37,11 @@ namespace osu.Game.Graphics.Containers.Markdown } } + public override SpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14), + }; + public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer(); protected override MarkdownFencedCodeBlock CreateFencedCodeBlock(FencedCodeBlock fencedCodeBlock) => new OsuMarkdownFencedCodeBlock(fencedCodeBlock); diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 535e1fb9a0..5165fe74a2 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -4,6 +4,8 @@ using Markdig.Syntax.Inlines; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; using osu.Game.Overlays; namespace osu.Game.Graphics.Containers.Markdown @@ -13,6 +15,11 @@ namespace osu.Game.Graphics.Containers.Markdown [Resolved] private OverlayColourProvider colourProvider { get; set; } + protected override SpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14), + }; + protected override void AddLinkText(string text, LinkInline linkInline) => AddDrawable(new OsuMarkdownLinkText(text, linkInline)); From b97d3f2af1f64453a0ee0585d21e322b6dfc96fa Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 3 May 2021 09:35:26 +0700 Subject: [PATCH 291/708] add heading test scene --- .../UserInterface/TestSceneOsuMarkdownContainer.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 99bbc22125..d64c243005 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -41,6 +41,19 @@ namespace osu.Game.Tests.Visual.UserInterface }; }); + [Test] + public void TestHeading() + { + AddStep("Add Heading", () => + { + markdownContainer.Text = @"# Header 1 +## Header 2 +### Header 3 +#### Header 4 +##### Header 5"; + }); + } + [Test] public void TestLink() { From b497785416df7d30523f11c37dd63f59766d370c Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 3 May 2021 09:35:55 +0700 Subject: [PATCH 292/708] add OsuMarkdownHeading --- .../Containers/Markdown/OsuMarkdownContainer.cs | 2 ++ .../Containers/Markdown/OsuMarkdownHeading.cs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index c4190d8c4f..3ad00e4df2 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -44,6 +44,8 @@ namespace osu.Game.Graphics.Containers.Markdown public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer(); + protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock); + protected override MarkdownFencedCodeBlock CreateFencedCodeBlock(FencedCodeBlock fencedCodeBlock) => new OsuMarkdownFencedCodeBlock(fencedCodeBlock); protected override MarkdownSeparator CreateSeparator(ThematicBreakBlock thematicBlock) => new OsuMarkdownSeparator(); diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs new file mode 100644 index 0000000000..bff0413e56 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Syntax; +using osu.Framework.Graphics.Containers.Markdown; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownHeading : MarkdownHeading + { + public OsuMarkdownHeading(HeadingBlock headingBlock) + : base(headingBlock) + { + } + } +} From 3e7df3bf026b205d2922192e16567ebb05bdb02b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 3 May 2021 09:35:59 +0700 Subject: [PATCH 293/708] change heading font size Heading 1 : 30px - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/base.less#L12-L16 Heading 2 : 26px - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/bem/osu-md.less#L133-L134 - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/variables.less#L169 Heading 3 : 20px - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/bem/osu-md.less#L147-L148 - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/variables.less#L170 - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/variables.less#L154 Heading 4 : 18px - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/bem/osu-md.less#L160-L161 - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/variables.less#L171 - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/variables.less#L153 Heading 5 : 16px - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/bem/osu-md.less#L174-L175 - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/variables.less#L172 - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/variables.less#L152 Heading 6 : 14px - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/bem/osu-md.less#L183-L184 - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/variables.less#L173 - https://github.com/ppy/osu-web/blob/31579d45aa464a439e943250ca0d5fa7e05a1eb6/resources/assets/less/variables.less#L150 --- .../Containers/Markdown/OsuMarkdownHeading.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs index bff0413e56..e7aa2b2512 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs @@ -12,5 +12,31 @@ namespace osu.Game.Graphics.Containers.Markdown : base(headingBlock) { } + + protected override float GetFontSizeByLevel(int level) + { + const float base_font_size = 14; + + switch (level) + { + case 1: + return 30 / base_font_size; + + case 2: + return 26 / base_font_size; + + case 3: + return 20 / base_font_size; + + case 4: + return 18 / base_font_size; + + case 5: + return 16 / base_font_size; + + default: + return 1; + } + } } } From 6da4105da6c4b2bcf53f5b2dbc61cfe5b22533c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 13:38:53 +0900 Subject: [PATCH 294/708] Remove Sync namespace (feels unnecessary) --- osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 1 - .../Spectate/{Sync => }/CatchUpSpectatorPlayerClock.cs | 2 +- .../Multiplayer/Spectate/{Sync => }/CatchUpSyncManager.cs | 2 +- .../Multiplayer/Spectate/{Sync => }/ISpectatorPlayerClock.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/{Sync => }/ISyncManager.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs | 1 - .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 1 - osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 1 - 9 files changed, 5 insertions(+), 9 deletions(-) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{Sync => }/CatchUpSpectatorPlayerClock.cs (97%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{Sync => }/CatchUpSyncManager.cs (98%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{Sync => }/ISpectatorPlayerClock.cs (93%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{Sync => }/ISyncManager.cs (94%) diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index 75ba362146..d4e591cf09 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -6,7 +6,7 @@ using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Tests.Visual; namespace osu.Game.Tests.OnlinePlay diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index a4856d1ee8..db431c0a82 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -17,7 +17,6 @@ using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; -using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Users; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs similarity index 97% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSpectatorPlayerClock.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs index e2a6b7c9ae..9e1a020eca 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs @@ -7,7 +7,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Timing; -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { /// /// A which catches up using rate adjustment. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs similarity index 98% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 5912c0803b..6028bc84f7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -6,7 +6,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Timing; -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { /// /// A which synchronises de-synced player clocks through catchup. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs similarity index 93% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorPlayerClock.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs index 311969c92c..1a5231e602 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs @@ -4,7 +4,7 @@ using osu.Framework.Bindables; using osu.Framework.Timing; -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { /// /// A clock which is used by s and managed by an . diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs similarity index 94% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISyncManager.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs index 1b83ea8cf3..bd698108f6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/Sync/ISyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs @@ -3,7 +3,7 @@ using osu.Framework.Timing; -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { /// /// Manages the synchronisation between one or more s in relation to a master clock. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index a17519c3aa..0fe9e01d9d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Scoring; -using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index ef31a5a7f0..fd2c79f8e4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; -using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; using osu.Game.Screens.Spectate; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 89c69cc666..50b5fc2110 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -15,7 +15,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; -using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate From 66ae6e58d18b8c1f5be9d73095c2ea9aac73791c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:01:10 +0900 Subject: [PATCH 295/708] Reword comment regarding LoadRequested special case to be easier to understand context --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index b8347d0537..e6ce135660 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -413,7 +413,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onLoadRequested() { - // If the user is spectating, the multi-spectator screen may still be the current screen. + // In the case of spectating, IMultiplayerClient.LoadRequested can be fired while the game is still spectating a previous session. + // For now, we want to game to switch to the new game so need to request exiting from the play screen. if (!ParentScreen.IsCurrentScreen()) { ParentScreen.MakeCurrent(); From dc5ee31d946918c2cbb2d0727e6039544b3a38d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:04:20 +0900 Subject: [PATCH 296/708] Use switch for screen construction --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index e6ce135660..ab22d40181 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -433,10 +433,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Debug.Assert(client.LocalUser != null); - if (client.LocalUser.State == MultiplayerUserState.Spectating) - return new MultiSpectatorScreen(client.CurrentMatchPlayingUserIds.ToArray()); + int[] userIds = client.CurrentMatchPlayingUserIds.ToArray(); - return new MultiplayerPlayer(SelectedItem.Value, client.CurrentMatchPlayingUserIds.ToArray()); + switch (client.LocalUser.State) + { + case MultiplayerUserState.Spectating: + return new MultiSpectatorScreen(userIds); + + default: + return new MultiplayerPlayer(SelectedItem.Value, userIds); + } } protected override void Dispose(bool isDisposing) From c065092e72f67e41d02e6d3a75637f7722c26b4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:25:52 +0900 Subject: [PATCH 297/708] Fix weird access to userIds in `MultiplayerSpectatorScreen` --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 16 ++++++---------- osu.Game/Screens/Spectate/SpectatorScreen.cs | 6 +++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index fd2c79f8e4..be4450787a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -27,8 +27,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); - private readonly int[] userIds; - [Resolved] private SpectatorStreamingClient spectatorClient { get; set; } @@ -49,8 +47,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public MultiSpectatorScreen(int[] userIds) : base(userIds.Take(PlayerGrid.MAX_PLAYERS).ToArray()) { - this.userIds = GetUserIds().ToArray(); - instances = new PlayerArea[this.userIds.Length]; + instances = new PlayerArea[UserIds.Count]; } [BackgroundDependencyLoader] @@ -84,9 +81,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate }) }; - for (int i = 0; i < userIds.Length; i++) + for (int i = 0; i < UserIds.Count; i++) { - grid.Add(instances[i] = new PlayerArea(userIds[i], masterClockContainer.GameplayClock)); + grid.Add(instances[i] = new PlayerArea(UserIds[i], masterClockContainer.GameplayClock)); syncManager.AddPlayerClock(instances[i].GameplayClock); } @@ -95,7 +92,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor(); scoreProcessor.ApplyBeatmap(playableBeatmap); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, userIds) { Expanded = { Value = true } }, leaderboardContainer.Add); + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, UserIds.ToArray()) { Expanded = { Value = true } }, leaderboardContainer.Add); } protected override void LoadComplete() @@ -130,7 +127,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void StartGameplay(int userId, GameplayState gameplayState) { - var instance = instances[getIndexForUser(userId)]; + var instance = instances.Single(i => i.UserId == userId); + instance.LoadScore(gameplayState.Score); syncManager.AddPlayerClock(instance.GameplayClock); @@ -149,7 +147,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate multiplayerClient.ChangeState(MultiplayerUserState.Idle); return base.OnBackButton(); } - - private int getIndexForUser(int userId) => Array.IndexOf(userIds, userId); } } diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 4aca379ebe..bcebd51954 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; -using NuGet.Packaging; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -27,8 +26,9 @@ namespace osu.Game.Screens.Spectate /// public abstract class SpectatorScreen : OsuScreen { - protected IEnumerable GetUserIds() => userIds; - private readonly HashSet userIds = new HashSet(); + protected IReadOnlyList UserIds => userIds; + + private readonly List userIds = new List(); [Resolved] private BeatmapManager beatmaps { get; set; } From 8c9cfb63013df07e0942e07fdb10918299cc034d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:28:35 +0900 Subject: [PATCH 298/708] 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 2aa21e2affb3f9993ec42b87228236e768aa7e05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:37:11 +0900 Subject: [PATCH 299/708] Adjust documentation in `CatchUpSyncManager` --- .../OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index 6028bc84f7..efc12eaaa5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public class CatchUpSyncManager : Component, ISyncManager { /// - /// The offset from the master clock to which player clocks should be synchronised to. + /// The offset from the master clock to which player clocks should remain within to be considered in-sync. /// public const double SYNC_TARGET = 16; @@ -102,6 +102,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate for (int i = 0; i < playerClocks.Count; i++) { var clock = playerClocks[i]; + + // How far this player's clock is out of sync, compared to the master clock. + // A negative value means the player is running fast (ahead); a positive value means the player is running behind (catching up). double timeDelta = MasterClock.CurrentTime - clock.CurrentTime; // Check that the player clock isn't too far ahead. From b1a19b6dd6f6ea6782828fdd1cc23883bf9c947a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:41:55 +0900 Subject: [PATCH 300/708] Add xmldoc for `PlayerIsolationContainer` --- osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 50b5fc2110..fe79e5db72 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -110,6 +110,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public override bool PropagatePositionalInputSubTree => false; public override bool PropagateNonPositionalInputSubTree => false; + /// + /// Isolates each player instance from the game-wide ruleset/beatmap/mods (to allow for different players having different settings). + /// private class PlayerIsolationContainer : Container { [Cached] From 54abf8f6f685a31487579d5f5bde1db7d4d67479 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:48:04 +0900 Subject: [PATCH 301/708] Vertically centre leaderboard for now --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index be4450787a..8c7b7bab01 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -92,7 +92,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor(); scoreProcessor.ApplyBeatmap(playableBeatmap); - LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, UserIds.ToArray()) { Expanded = { Value = true } }, leaderboardContainer.Add); + LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, UserIds.ToArray()) + { + Expanded = { Value = true }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, leaderboardContainer.Add); } protected override void LoadComplete() From f3b305bbe62f4501579cd36ca6e77c188fb344ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:58:23 +0900 Subject: [PATCH 302/708] 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 303/708] 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 304/708] 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 305/708] 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 306/708] 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 307/708] 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 308/708] 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 309/708] 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 310/708] 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 311/708] 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 312/708] 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 313/708] 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 314/708] 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 625890381f306823600468a8e590241ab88694d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 16:47:47 +0900 Subject: [PATCH 315/708] Update `ComboCounter` components to use DI to attach data source --- .../Visual/Gameplay/TestSceneComboCounter.cs | 24 +++++++------------ .../Visual/Gameplay/TestSceneHUDOverlay.cs | 12 ++++++---- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 20 ++++++---------- .../Screens/Play/HUD/DefaultComboCounter.cs | 7 +++++- .../Screens/Play/HUD/LegacyComboCounter.cs | 6 +++-- .../Screens/Play/HUD/SkinnableComboCounter.cs | 15 +----------- osu.Game/Screens/Play/HUDOverlay.cs | 12 ++++------ osu.Game/Screens/Play/Player.cs | 2 ++ 8 files changed, 41 insertions(+), 57 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs index d0c2fb5064..b0a0b5189f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs @@ -4,9 +4,11 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay @@ -17,31 +19,21 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(); + [SetUpSteps] public void SetUpSteps() { - AddStep("Create combo counters", () => SetContents(() => - { - var comboCounter = new SkinnableComboCounter(); - comboCounter.Current.Value = 1; - return comboCounter; - })); + AddStep("Create combo counters", () => SetContents(() => new SkinnableComboCounter())); } [Test] public void TestComboCounterIncrementing() { - AddRepeatStep("increase combo", () => - { - foreach (var counter in comboCounters) - counter.Current.Value++; - }, 10); + AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10); - AddStep("reset combo", () => - { - foreach (var counter in comboCounters) - counter.Current.Value = 0; - }); + AddStep("reset combo", () => scoreProcessor.Combo.Value = 0); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 3cefb8623f..55c681b605 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osuTK.Input; @@ -19,6 +20,9 @@ namespace osu.Game.Tests.Visual.Gameplay { private HUDOverlay hudOverlay; + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(); + // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); @@ -31,9 +35,9 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - AddRepeatStep("increase combo", () => { hudOverlay.ComboCounter.Current.Value++; }, 10); + AddRepeatStep("increase combo", () => { scoreProcessor.Combo.Value++; }, 10); - AddStep("reset combo", () => { hudOverlay.ComboCounter.Current.Value = 0; }); + AddStep("reset combo", () => { scoreProcessor.Combo.Value = 0; }); } [Test] @@ -139,12 +143,12 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create overlay", () => { - hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + hudOverlay = new HUDOverlay(scoreProcessor, 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; + scoreProcessor.Combo.Value = 1; action?.Invoke(hudOverlay); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index fec1610160..8131c77b4b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -14,6 +14,7 @@ using osu.Game.Configuration; 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 osuTK.Input; @@ -23,6 +24,9 @@ namespace osu.Game.Tests.Visual.Gameplay { private HUDOverlay hudOverlay; + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(); + private IEnumerable hudOverlays => CreatedDrawables.OfType(); // best way to check without exposing. @@ -37,17 +41,9 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - AddRepeatStep("increase combo", () => - { - foreach (var hud in hudOverlays) - hud.ComboCounter.Current.Value++; - }, 10); + AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10); - AddStep("reset combo", () => - { - foreach (var hud in hudOverlays) - hud.ComboCounter.Current.Value = 0; - }); + AddStep("reset combo", () => scoreProcessor.Combo.Value = 0); } [Test] @@ -80,13 +76,11 @@ namespace osu.Game.Tests.Visual.Gameplay { SetContents(() => { - hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + hudOverlay = new HUDOverlay(scoreProcessor, 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; - action?.Invoke(hudOverlay); return hudOverlay; diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 63e7a88550..d0c26afe5e 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Screens.Play.HUD @@ -24,7 +25,11 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.BlueLighter; + private void load(OsuColour colours, ScoreProcessor scoreProcessor) + { + Colour = colours.BlueLighter; + Current.BindTo(scoreProcessor.Combo); + } protected override void Update() { diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index b4604c0d01..58a30aea94 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osuTK; @@ -79,7 +80,7 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load() + private void load(ScoreProcessor scoreProcessor) { InternalChildren = new[] { @@ -95,7 +96,8 @@ namespace osu.Game.Screens.Play.HUD }, }; - Current.ValueChanged += combo => updateCount(combo.NewValue == 0); + Current.BindTo(scoreProcessor.Combo); + Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs index c04c50141a..c62f1460c9 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs @@ -1,29 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Bindables; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableComboCounter : SkinnableDrawable, IComboCounter + public class SkinnableComboCounter : SkinnableDrawable { - public Bindable Current { get; } = new Bindable(); - public SkinnableComboCounter() : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), skinComponent => new DefaultComboCounter()) { CentreComponent = false; } - - private IComboCounter skinnedCounter; - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - skinnedCounter = Drawable as IComboCounter; - skinnedCounter?.Current.BindTo(Current); - } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 669c920017..83897c5167 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.Play public float TopScoringElementsHeight { get; private set; } public readonly KeyCounterDisplay KeyCounter; - public readonly SkinnableComboCounter ComboCounter; public readonly SkinnableScoreCounter ScoreCounter; public readonly SkinnableAccuracyCounter AccuracyCounter; public readonly SkinnableHealthDisplay HealthDisplay; @@ -106,7 +105,7 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), AccuracyCounter = CreateAccuracyCounter(), ScoreCounter = CreateScoreCounter(), - ComboCounter = CreateComboCounter(), + CreateComboCounter(), HitErrorDisplay = CreateHitErrorDisplayOverlay(), } }, @@ -276,13 +275,13 @@ namespace osu.Game.Screens.Play Progress.BindDrawableRuleset(drawableRuleset); } - protected virtual SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter(); + protected SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter(); - protected virtual SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter(); + protected SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter(); - protected virtual SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter(); + protected SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter(); - protected virtual SkinnableHealthDisplay CreateHealthDisplay() => new SkinnableHealthDisplay(); + protected SkinnableHealthDisplay CreateHealthDisplay() => new SkinnableHealthDisplay(); protected virtual FailingLayer CreateFailingLayer() => new FailingLayer { @@ -323,7 +322,6 @@ namespace osu.Game.Screens.Play { ScoreCounter?.Current.BindTo(processor.TotalScore); AccuracyCounter?.Current.BindTo(processor.Accuracy); - ComboCounter?.Current.BindTo(processor.Combo); if (HealthDisplay is IHealthDisplay shd) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 27a4fcc291..c9d7bbf4c1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -202,6 +202,8 @@ namespace osu.Game.Screens.Play ScoreProcessor.ApplyBeatmap(playableBeatmap); ScoreProcessor.Mods.BindTo(Mods); + dependencies.CacheAs(ScoreProcessor); + HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); From 840c22a3b17bd01dea5b79c4a6bdd0e124b04241 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 May 2021 12:16:40 +0300 Subject: [PATCH 316/708] Add back mis-removed fade transform --- .../Compose/Components/SelectionBoxDragHandleContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs index 151c169a33..7db2cdbbf5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (activeHandle?.InOperation == true || activeHandle?.IsHovered == true) return; - displayedRotationHandle?.Hide(); + displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint); displayedRotationHandle = null; activeHandle = allDragHandles.SingleOrDefault(h => h.InOperation); @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (activeHandle != null) { displayedRotationHandle = getCorrespondingRotationHandle(activeHandle, rotationHandles); - displayedRotationHandle?.Show(); + displayedRotationHandle?.FadeIn(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint); } } From b28e1569caad7b6a1781dc32129f57a9d74cc92c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 20:09:48 +0900 Subject: [PATCH 317/708] 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 ca4b860920c2bd7f1efe518e65347904ab8fd7a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 20:11:24 +0900 Subject: [PATCH 318/708] Move `BindValueChanged` call to `LoadComplete` --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 58a30aea94..0bccbd0338 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -97,7 +97,6 @@ namespace osu.Game.Screens.Play.HUD }; Current.BindTo(scoreProcessor.Combo); - Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); } protected override void LoadComplete() @@ -111,7 +110,7 @@ namespace osu.Game.Screens.Play.HUD popOutCount.Origin = Origin; popOutCount.Anchor = Anchor; - updateCount(false); + Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); } private void updateCount(bool rolling) From 356b1e9a2dd3492039fd7f0bd1e1112856ca2e55 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 4 May 2021 05:43:59 +0700 Subject: [PATCH 319/708] add emphases test --- .../UserInterface/TestSceneOsuMarkdownContainer.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index d64c243005..9fb14efe4d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -41,6 +41,20 @@ namespace osu.Game.Tests.Visual.UserInterface }; }); + [Test] + public void TestEmphases() + { + AddStep("Emphases", () => + { + markdownContainer.Text = @"_italic with underscore_ +*italic with asterisk* +__bold with underscore__ +**bold with asterisk** +*__italic with asterisk, bold with underscore__* +_**italic with underscore, bold with asterisk**_"; + }); + } + [Test] public void TestHeading() { From bfc328c5ab94eda41bfb5b81e84c66ca975373c0 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 4 May 2021 09:09:51 +0700 Subject: [PATCH 320/708] change font weight for bold text --- .../Containers/Markdown/OsuMarkdownTextFlowContainer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 5165fe74a2..517143c2db 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -26,5 +26,12 @@ namespace osu.Game.Graphics.Containers.Markdown // TODO : Add background (colour B6) and change font to monospace protected override void AddCodeInLine(CodeInline codeInline) => AddText(codeInline.Content, t => { t.Colour = colourProvider.Light1; }); + + protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic) + { + var spriteText = CreateSpriteText(); + spriteText.Font = spriteText.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic); + return spriteText; + } } } From 63381ff4f2a62335e851f266f84f8f422ec68e01 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 4 May 2021 09:34:21 +0700 Subject: [PATCH 321/708] change heading font weight h1 and h2 : Semi Bold (600) - https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/bem/osu-md.less#L111 - https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/bem/osu-md.less#L135 The rest of heading : Bold (700) - https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/bem/osu-md.less#L97 --- .../Containers/Markdown/OsuMarkdownHeading.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs index e7aa2b2512..f71c6753f4 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs @@ -3,16 +3,25 @@ using Markdig.Syntax; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownHeading : MarkdownHeading { + private readonly int level; + public OsuMarkdownHeading(HeadingBlock headingBlock) : base(headingBlock) { + level = headingBlock.Level; } + public override MarkdownTextFlowContainer CreateTextFlow() => new HeadingTextFlowContainer + { + Weight = GetFontWeightByLevel(level), + }; + protected override float GetFontSizeByLevel(int level) { const float base_font_size = 14; @@ -38,5 +47,30 @@ namespace osu.Game.Graphics.Containers.Markdown return 1; } } + + protected virtual FontWeight GetFontWeightByLevel(int level) + { + switch (level) + { + case 1: + case 2: + return FontWeight.SemiBold; + + default: + return FontWeight.Bold; + } + } + + private class HeadingTextFlowContainer : OsuMarkdownTextFlowContainer + { + public FontWeight Weight { get; set; } + + protected override SpriteText CreateSpriteText() + { + var spriteText = base.CreateSpriteText(); + spriteText.Font = spriteText.Font.With(weight: Weight); + return spriteText; + } + } } } From fd7a6b3a7c953c682154395abc517607dfff66bf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 May 2021 12:19:34 +0300 Subject: [PATCH 322/708] Finish transforms on controls load complete --- osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 38ed23fa13..9cba07e267 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -54,7 +54,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void LoadComplete() { base.LoadComplete(); + UpdateHoverState(); + FinishTransforms(true); } protected override bool OnHover(HoverEvent e) From 5f33c3514ec43a3df22ade1dc86e2a8b384c0c83 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 4 May 2021 06:37:18 +0300 Subject: [PATCH 323/708] Move selection box control internal events to drag handles --- .../Compose/Components/SelectionBoxControl.cs | 7 ------- .../Components/SelectionBoxDragHandle.cs | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 9cba07e267..4406cc055d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -22,9 +22,6 @@ namespace osu.Game.Screens.Edit.Compose.Components public event Action OperationStarted; public event Action OperationEnded; - internal event Action HoverGained; - internal event Action HoverLost; - private Circle circle; /// @@ -62,14 +59,11 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnHover(HoverEvent e) { UpdateHoverState(); - HoverGained?.Invoke(); return true; } protected override void OnHoverLost(HoverLostEvent e) { - base.OnHoverLost(e); - HoverLost?.Invoke(); UpdateHoverState(); } @@ -89,7 +83,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { HandlingMouse = false; UpdateHoverState(); - base.OnMouseUp(e); } protected virtual void UpdateHoverState() diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs index d7e58df748..ed7c451b7e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs @@ -29,5 +29,25 @@ namespace osu.Game.Screens.Edit.Compose.Components UpdateHoverState(); base.OnDragEnd(e); } + + #region Internal events for SelectionBoxDragHandleContainer + + internal event Action HoverGained; + internal event Action HoverLost; + + protected override bool OnHover(HoverEvent e) + { + bool result = base.OnHover(e); + HoverGained?.Invoke(); + return result; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + HoverLost?.Invoke(); + } + + #endregion } } From b2a0c2b5631ac3a3e5d746909d141fafbf655ce3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 4 May 2021 06:40:43 +0300 Subject: [PATCH 324/708] Consider drag handles active using mouse down instead of when dragged --- .../Compose/Components/SelectionBoxButton.cs | 2 +- .../Compose/Components/SelectionBoxControl.cs | 29 +++++-------------- .../Components/SelectionBoxDragHandle.cs | 15 ++++++++++ .../SelectionBoxDragHandleContainer.cs | 8 ++--- .../Components/SelectionBoxRotationHandle.cs | 2 +- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index fbbebe3288..43cbbb617b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void UpdateHoverState() { base.UpdateHoverState(); - icon.FadeColour(!HandlingMouse && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); + icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } public string TooltipText { get; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 4406cc055d..159886648e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -25,9 +25,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private Circle circle; /// - /// Whether this control is currently being operated on by the user. + /// Whether the user is currently holding the control with mouse. /// - public bool InOperation { get; private set; } + public bool IsHeld { get; private set; } [Resolved] protected OsuColour Colours { get; private set; } @@ -67,44 +67,31 @@ namespace osu.Game.Screens.Edit.Compose.Components UpdateHoverState(); } - /// - /// Whether this control is currently handling mouse down input. - /// - protected bool HandlingMouse { get; private set; } - protected override bool OnMouseDown(MouseDownEvent e) { - HandlingMouse = true; + IsHeld = true; UpdateHoverState(); return true; } protected override void OnMouseUp(MouseUpEvent e) { - HandlingMouse = false; + IsHeld = false; UpdateHoverState(); } protected virtual void UpdateHoverState() { - if (HandlingMouse) + if (IsHeld) circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint); else circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint); - this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint); + this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint); } - protected void OnOperationStarted() - { - InOperation = true; - OperationStarted?.Invoke(); - } + protected void OnOperationStarted() => OperationStarted?.Invoke(); - protected void OnOperationEnded() - { - InOperation = false; - OperationEnded?.Invoke(); - } + protected void OnOperationEnded() => OperationEnded?.Invoke(); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs index ed7c451b7e..3c1741e24d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs @@ -34,6 +34,8 @@ namespace osu.Game.Screens.Edit.Compose.Components internal event Action HoverGained; internal event Action HoverLost; + internal event Action MouseDown; + internal event Action MouseUp; protected override bool OnHover(HoverEvent e) { @@ -48,6 +50,19 @@ namespace osu.Game.Screens.Edit.Compose.Components HoverLost?.Invoke(); } + protected override bool OnMouseDown(MouseDownEvent e) + { + bool result = base.OnMouseDown(e); + MouseDown?.Invoke(); + return result; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + base.OnMouseUp(e); + MouseUp?.Invoke(); + } + #endregion } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs index 7db2cdbbf5..c78514b7db 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs @@ -62,8 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { handle.HoverGained += updateRotationHandlesVisibility; handle.HoverLost += updateRotationHandlesVisibility; - handle.OperationStarted += updateRotationHandlesVisibility; - handle.OperationEnded += updateRotationHandlesVisibility; + handle.MouseDown += updateRotationHandlesVisibility; + handle.MouseUp += updateRotationHandlesVisibility; allDragHandles.Add(handle); } @@ -72,13 +72,13 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateRotationHandlesVisibility() { - if (activeHandle?.InOperation == true || activeHandle?.IsHovered == true) + if (activeHandle?.IsHeld == true || activeHandle?.IsHovered == true) return; displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint); displayedRotationHandle = null; - activeHandle = allDragHandles.SingleOrDefault(h => h.InOperation); + activeHandle = allDragHandles.SingleOrDefault(h => h.IsHeld); activeHandle ??= allDragHandles.SingleOrDefault(h => h.IsHovered); if (activeHandle != null) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index 6303caf9ed..65a54292ab 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void UpdateHoverState() { base.UpdateHoverState(); - icon.FadeColour(!HandlingMouse && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); + icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint); } } } From 8abff4881b731819535c9f603dc820c193abb010 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 4 May 2021 07:31:52 +0300 Subject: [PATCH 325/708] Hide the corresponding rotation handle when holding scale handle --- .../Editing/TestSceneComposeSelectBox.cs | 29 +++++++++++++++---- .../SelectionBoxDragHandleContainer.cs | 10 +++++-- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 914b8b6f27..efcd7864d7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Game.Screens.Edit.Compose.Components; using osuTK; using osuTK.Input; @@ -40,6 +41,7 @@ namespace osu.Game.Tests.Visual.Editing }; InputManager.MoveMouseTo(selectionBox); + InputManager.ReleaseButton(MouseButton.Left); }); private bool handleScale(Vector2 amount, Anchor reference) @@ -127,26 +129,41 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestDraggingScaleHandleKeepsCorrespondingRotationHandleShown() + public void TestHoldingScaleHandleHidesCorrespondingRotationHandle() { SelectionBoxRotationHandle rotationHandle = null; AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType().First()); AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0); - AddStep("hover over and hold closest scale handle", () => + AddStep("hover over closest scale handle", () => { InputManager.MoveMouseTo(this.ChildrenOfType().Single(s => s.Anchor == rotationHandle.Anchor)); - InputManager.PressButton(MouseButton.Left); }); AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1); + AddStep("hold scale handle", () => InputManager.PressButton(MouseButton.Left)); + AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0); - AddStep("drag to centre", () => InputManager.MoveMouseTo(selectionBox)); - AddAssert("rotation handle still shown", () => rotationHandle.Alpha > 0); + int i; + ScheduledDelegate mouseMove = null; + AddStep("start dragging", () => + { + i = 0; + + mouseMove = Scheduler.AddDelayed(() => + { + InputManager.MoveMouseTo(selectionBox.ScreenSpaceDrawQuad.TopLeft + Vector2.One * (5 * ++i)); + }, 100, true); + }); + AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0); + + AddStep("end dragging", () => mouseMove.Cancel()); + AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0); AddStep("unhold left", () => InputManager.ReleaseButton(MouseButton.Left)); + AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1); AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox, new Vector2(20))); - AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0); + AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs index c78514b7db..456f72878d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs @@ -72,13 +72,19 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateRotationHandlesVisibility() { - if (activeHandle?.IsHeld == true || activeHandle?.IsHovered == true) + // if the active handle is a rotation handle and is held or hovered, + // then no need to perform any updates to the rotation handles visibility. + if (activeHandle is SelectionBoxRotationHandle && (activeHandle?.IsHeld == true || activeHandle?.IsHovered == true)) return; displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint); displayedRotationHandle = null; - activeHandle = allDragHandles.SingleOrDefault(h => h.IsHeld); + // if the active handle is not a rotation handle but is held, then keep the rotation handle hidden. + if (activeHandle?.IsHeld == true) + return; + + activeHandle = rotationHandles.SingleOrDefault(h => h.IsHeld || h.IsHovered); activeHandle ??= allDragHandles.SingleOrDefault(h => h.IsHovered); if (activeHandle != null) From e00af3e71d2f2f754e641698dede05e3ac089b5b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 4 May 2021 09:45:58 +0300 Subject: [PATCH 326/708] 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 327/708] 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 328/708] 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 329/708] 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 330/708] 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 331/708] 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 332/708] 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 333/708] 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 334/708] 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 335/708] 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 336/708] 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 337/708] 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 338/708] 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 339/708] 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 340/708] 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 12c1ded7a8f3c6c6e76cee5125d15a57530e70f7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 00:28:49 +0300 Subject: [PATCH 341/708] Fix test scene broken on master --- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index efcd7864d7..e383aa8008 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -30,6 +30,8 @@ namespace osu.Game.Tests.Visual.Editing { selectionBox = new SelectionBox { + RelativeSizeAxes = Axes.Both, + CanRotate = true, CanScaleX = true, CanScaleY = true, From 1472960319ecdd6f4627bdfd7aff9896b6bdcfe1 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Tue, 4 May 2021 21:35:36 -0400 Subject: [PATCH 342/708] 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 343/708] 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 344/708] 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 345/708] 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 346/708] 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 347/708] 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 348/708] 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 349/708] 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 350/708] 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 351/708] 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 352/708] 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 353/708] 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 354/708] 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 355/708] 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 356/708] 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 357/708] 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 358/708] 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 359/708] 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 360/708] 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 2a67361dc01963e10e850ac5523858578c613f67 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 21:50:11 +0300 Subject: [PATCH 361/708] OnOperation -> TriggerOperation --- .../Screens/Edit/Compose/Components/SelectionBoxButton.cs | 4 ++-- .../Screens/Edit/Compose/Components/SelectionBoxControl.cs | 4 ++-- .../Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs index 43cbbb617b..3b1dae6c3d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxButton.cs @@ -46,9 +46,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnClick(ClickEvent e) { - OnOperationStarted(); + TriggerOperationStarted(); Action?.Invoke(); - OnOperationEnded(); + TriggerOperatoinEnded(); return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs index 159886648e..40d367bb80 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxControl.cs @@ -90,8 +90,8 @@ namespace osu.Game.Screens.Edit.Compose.Components this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint); } - protected void OnOperationStarted() => OperationStarted?.Invoke(); + protected void TriggerOperationStarted() => OperationStarted?.Invoke(); - protected void OnOperationEnded() => OperationEnded?.Invoke(); + protected void TriggerOperatoinEnded() => OperationEnded?.Invoke(); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs index 3c1741e24d..65a95951cf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandle.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnDragStart(DragStartEvent e) { - OnOperationStarted(); + TriggerOperationStarted(); return true; } @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void OnDragEnd(DragEndEvent e) { - OnOperationEnded(); + TriggerOperatoinEnded(); UpdateHoverState(); base.OnDragEnd(e); From 266d8d828297b9381520023a47bbe670ffce548e Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 05:13:39 +0700 Subject: [PATCH 362/708] move list item constant position --- .../Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index 98b1fd1381..84565a577a 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -12,13 +12,13 @@ namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownListItem : CompositeDrawable { + private const float ordered_left_padding = 30; + private const float unordered_left_padding = 20; + private readonly int level; private readonly int order; private readonly bool isOrdered; - private const float ordered_left_padding = 30; - private const float unordered_left_padding = 20; - [Resolved] private IMarkdownTextComponent parentTextComponent { get; set; } From 3ddf551b0360322fb15d9802949ed35997624b94 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 05:13:54 +0700 Subject: [PATCH 363/708] remove unused this --- osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index 84565a577a..a6274de3da 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Containers.Markdown public OsuMarkdownListItem(ListItemBlock listItemBlock, int level) { this.level = level; - this.order = listItemBlock.Order; + order = listItemBlock.Order; isOrdered = ((ListBlock)listItemBlock.Parent).IsOrdered; AutoSizeAxes = Axes.Y; From 99e0cc9bbe4106362a854caae272c929a4ac6e88 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 05:15:32 +0700 Subject: [PATCH 364/708] rename CreateTextMarker to GetTextMarker --- osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index a6274de3da..a4941ef785 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.Containers.Markdown private void load() { var marker = parentTextComponent.CreateSpriteText(); - marker.Text = CreateTextMarker(); + marker.Text = GetTextMarker(); if (isOrdered) { @@ -67,7 +67,7 @@ namespace osu.Game.Graphics.Containers.Markdown AddInternal(marker); } - protected virtual string CreateTextMarker() + protected virtual string GetTextMarker() { if (isOrdered) { From 9bb80492c534bc162cb28085bb6631c07e9fc6bf Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 05:29:29 +0700 Subject: [PATCH 365/708] add level and isOrdered parameter --- osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index a4941ef785..a7582cb4b3 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.Containers.Markdown private void load() { var marker = parentTextComponent.CreateSpriteText(); - marker.Text = GetTextMarker(); + marker.Text = GetTextMarker(level, isOrdered); if (isOrdered) { @@ -67,7 +67,7 @@ namespace osu.Game.Graphics.Containers.Markdown AddInternal(marker); } - protected virtual string GetTextMarker() + protected virtual string GetTextMarker(int level, bool isOrdered) { if (isOrdered) { From 4567abe3dbd39c1f26ce42003edd7b021a7c385d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 05:32:07 +0700 Subject: [PATCH 366/708] add xmldoc for GetTextMarker --- .../Graphics/Containers/Markdown/OsuMarkdownListItem.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index a7582cb4b3..b586bb7f30 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -67,6 +67,12 @@ namespace osu.Game.Graphics.Containers.Markdown AddInternal(marker); } + /// + /// Get text marker based on and . + /// + /// The markdown level of current list item. + /// Is true if the list item is an ordered list. + /// protected virtual string GetTextMarker(int level, bool isOrdered) { if (isOrdered) From cfd28c51bbbb827c0394282ec144ebdf3a9557f6 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 05:35:16 +0700 Subject: [PATCH 367/708] change block quote backgroudn width Reference : https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/base.less#L7-L10 --- .../Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs index 869cba82f2..d9b516892b 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs @@ -26,7 +26,9 @@ namespace osu.Game.Graphics.Containers.Markdown protected override Drawable CreateBackground() { - return background = base.CreateBackground(); + background = base.CreateBackground(); + background.Width = 2; + return background; } public override MarkdownTextFlowContainer CreateTextFlow() From 550e6c0fbb5e670d77dcc384c5dfbe3aba07f99b Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 05:36:46 +0700 Subject: [PATCH 368/708] change quote block margin padding to use vertical and horizontal --- .../Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs index d9b516892b..07bb72c2e6 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs @@ -36,10 +36,8 @@ namespace osu.Game.Graphics.Containers.Markdown var textFlow = base.CreateTextFlow(); textFlow.Margin = new MarginPadding { - Top = 10, - Bottom = 10, - Left = 20, - Right = 20, + Vertical = 10, + Horizontal = 20, }; return textFlow; } From 0d3ca8dde197428a2eef2ec2bc4d49d614ccea28 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 08:43:38 +0700 Subject: [PATCH 369/708] change font weight of table header --- .../Markdown/OsuMarkdownTableCell.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs index d8b9145228..fca85e02a8 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays; namespace osu.Game.Graphics.Containers.Markdown @@ -30,7 +31,6 @@ namespace osu.Game.Graphics.Containers.Markdown RelativeSizeAxes = Axes.X, }; - // TODO : Change font weight to 700 for heading if (isHeading) { border.Colour = colourProvider.Background3; @@ -48,5 +48,23 @@ namespace osu.Game.Graphics.Containers.Markdown AddInternal(border); } + + public override MarkdownTextFlowContainer CreateTextFlow() => new TableCellTextFlowContainer + { + Weight = isHeading ? FontWeight.Bold : FontWeight.Regular, + Padding = new MarginPadding(10), + }; + + private class TableCellTextFlowContainer : OsuMarkdownTextFlowContainer + { + public FontWeight Weight { get; set; } + + protected override SpriteText CreateSpriteText() + { + var spriteText = base.CreateSpriteText(); + spriteText.Font = spriteText.Font.With(weight: Weight); + return spriteText; + } + } } } From d4658c609b61d3f15cccbe840023bb6003efc91c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 5 May 2021 22:43:16 -0700 Subject: [PATCH 370/708] 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 ee23124bb14409ac6c4833df20b1259ecca888d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 May 2021 15:17:53 +0900 Subject: [PATCH 371/708] Remove no-longer-used interface --- .../Screens/Play/HUD/DefaultComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/IComboCounter.cs | 19 ------------------- .../Screens/Play/HUD/LegacyComboCounter.cs | 2 +- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/IComboCounter.cs diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index d0c26afe5e..5a975c1b80 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -12,7 +12,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { - public class DefaultComboCounter : RollingCounter, IComboCounter + public class DefaultComboCounter : RollingCounter { private readonly Vector2 offset = new Vector2(20, 5); diff --git a/osu.Game/Screens/Play/HUD/IComboCounter.cs b/osu.Game/Screens/Play/HUD/IComboCounter.cs deleted file mode 100644 index ff235bf04e..0000000000 --- a/osu.Game/Screens/Play/HUD/IComboCounter.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Bindables; -using osu.Framework.Graphics; - -namespace osu.Game.Screens.Play.HUD -{ - /// - /// An interface providing a set of methods to update a combo counter. - /// - public interface IComboCounter : IDrawable - { - /// - /// The current combo to be displayed. - /// - Bindable Current { get; } - } -} diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 0bccbd0338..8ecd960de1 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -15,7 +15,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 { public Bindable Current { get; } = new BindableInt { MinValue = 0, }; From af75c9ac82ab73f43188284eef35a67b7bf1dbfd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 May 2021 16:08:28 +0900 Subject: [PATCH 372/708] 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 @@ - + From b6560a616a22f07e248f2063cd0d78f1fbbac180 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 15:00:12 +0700 Subject: [PATCH 373/708] add comment for base font size heading --- osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs index f71c6753f4..a7fecc6e25 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs @@ -24,6 +24,9 @@ namespace osu.Game.Graphics.Containers.Markdown protected override float GetFontSizeByLevel(int level) { + // Reference for this font size + // https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/bem/osu-md.less#L9 + // https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/variables.less#L161 const float base_font_size = 14; switch (level) From e7c563fb671f8c2eee44de363e6c04971dad6cd3 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 15:11:45 +0700 Subject: [PATCH 374/708] simplify `CreateTextFlow` in quote block --- .../Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs index 07bb72c2e6..f8b8a1c2a2 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs @@ -33,13 +33,11 @@ namespace osu.Game.Graphics.Containers.Markdown public override MarkdownTextFlowContainer CreateTextFlow() { - var textFlow = base.CreateTextFlow(); - textFlow.Margin = new MarginPadding + return base.CreateTextFlow().With(f => f.Margin = new MarginPadding { Vertical = 10, Horizontal = 20, - }; - return textFlow; + }); } } } From 91283d41ceef2c9564d024b20d3ea893e38198e6 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 15:13:43 +0700 Subject: [PATCH 375/708] add paragraph test --- .../UserInterface/TestSceneOsuMarkdownContainer.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs index 9fb14efe4d..dc41f184f2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuMarkdownContainer.cs @@ -95,6 +95,19 @@ _**italic with underscore, bold with asterisk**_"; }); } + [Test] + public void TestParagraph() + { + AddStep("Add paragraph", () => + { + markdownContainer.Text = @"first paragraph + +second paragraph + +third paragraph"; + }); + } + [Test] public void TestFencedCodeBlock() { From ba634cbf11174ad90003405b0ff4a3c49cad7016 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 15:13:59 +0700 Subject: [PATCH 376/708] change line spacing to 21 We use margin bottom in osu-web markdown paragraph[1] as reference for this line spacing value. The value from osu-web itself is 1.5em[2]. Because the base font size of the paragraph is 14px[3][4], the actual value is 14 * 1.5 = 21px [1] https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/bem/osu-md.less#L230 [2] https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/variables.less#L58 [3] https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/bem/osu-md.less#L9 [4] https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/variables.less#L161 --- .../Graphics/Containers/Markdown/OsuMarkdownContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index 3ad00e4df2..c66f3fbca2 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -16,6 +16,11 @@ namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownContainer : MarkdownContainer { + public OsuMarkdownContainer() + { + LineSpacing = 21; + } + protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level) { switch (markdownObject) From 39067e6926e0950303e137140dded1f2eb2f5dd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 May 2021 17:19:18 +0900 Subject: [PATCH 377/708] Fix slider input handling potentially being offset after composer area resize Closes https://github.com/ppy/osu/issues/12671. --- .../Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 1c3d270c95..6e22c35ab3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -26,6 +26,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { AccentColour = Color4.Transparent }; + + // SliderSelectionBlueprint relies on calling ReceivePositionalInputAt on this drawable to determine whether selection should occur. + // Without AlwaysPresent, a movement in a parent container (ie. the editor composer area resizing) could cause incorrect input handling. + AlwaysPresent = true; } [BackgroundDependencyLoader] From 010f6258705e86c47ad208c519362daa80e6c346 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 17:05:41 +0700 Subject: [PATCH 378/708] use derived component in OsuMarkdownFencedCodeBlock --- .../Markdown/OsuMarkdownFencedCodeBlock.cs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs index ddd88dd915..0d67849060 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownFencedCodeBlock.cs @@ -12,33 +12,34 @@ namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownFencedCodeBlock : MarkdownFencedCodeBlock { - private Box background; - private MarkdownTextFlowContainer textFlow; - + // TODO : change to monospace font for this component public OsuMarkdownFencedCodeBlock(FencedCodeBlock fencedCodeBlock) : base(fencedCodeBlock) { } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - // TODO : Change to monospace font to match with osu-web - background.Colour = colourProvider.Background6; - textFlow.Colour = colourProvider.Light1; - } + protected override Drawable CreateBackground() => new CodeBlockBackground(); - protected override Drawable CreateBackground() + public override MarkdownTextFlowContainer CreateTextFlow() => new CodeBlockTextFlowContainer(); + + private class CodeBlockBackground : Box { - return background = new Box + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) { - RelativeSizeAxes = Axes.Both, - }; + RelativeSizeAxes = Axes.Both; + Colour = colourProvider.Background6; + } } - public override MarkdownTextFlowContainer CreateTextFlow() + private class CodeBlockTextFlowContainer : OsuMarkdownTextFlowContainer { - return textFlow = base.CreateTextFlow(); + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Colour = colourProvider.Light1; + Margin = new MarginPadding(10); + } } } } From 7b43730fe61791017693d058a101487057d218a3 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 17:13:46 +0700 Subject: [PATCH 379/708] add QuoteBackground in OsuMarkdownQuoteBlock --- .../Markdown/OsuMarkdownQuoteBlock.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs index f8b8a1c2a2..9935c81537 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownQuoteBlock.cs @@ -5,31 +5,19 @@ using Markdig.Syntax; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Shapes; using osu.Game.Overlays; namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownQuoteBlock : MarkdownQuoteBlock { - private Drawable background; - public OsuMarkdownQuoteBlock(QuoteBlock quoteBlock) : base(quoteBlock) { } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - background.Colour = colourProvider.Content2; - } - - protected override Drawable CreateBackground() - { - background = base.CreateBackground(); - background.Width = 2; - return background; - } + protected override Drawable CreateBackground() => new QuoteBackground(); public override MarkdownTextFlowContainer CreateTextFlow() { @@ -39,5 +27,18 @@ namespace osu.Game.Graphics.Containers.Markdown Horizontal = 20, }); } + + private class QuoteBackground : Box + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + RelativeSizeAxes = Axes.Y; + Width = 2; + Colour = colourProvider.Content2; + } + } } } From 92022f2cba0240a6fe25bc5607eeb2ff9e5e9225 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 17:17:14 +0700 Subject: [PATCH 380/708] add Separator component in OsuMarkdownSeparator --- .../Markdown/OsuMarkdownSeparator.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownSeparator.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownSeparator.cs index 9b28200452..28a87c9f21 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownSeparator.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownSeparator.cs @@ -4,23 +4,24 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Shapes; using osu.Game.Overlays; namespace osu.Game.Graphics.Containers.Markdown { public class OsuMarkdownSeparator : MarkdownSeparator { - private Drawable separator; + protected override Drawable CreateSeparator() => new Separator(); - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private class Separator : Box { - separator.Colour = colourProvider.Background3; - } - - protected override Drawable CreateSeparator() - { - return separator = base.CreateSeparator(); + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.X; + Height = 1; + Colour = colourProvider.Background3; + } } } } From 9be36230f9bd5825aafc5b5f664480d76bdf43fb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 6 May 2021 21:43:30 +0900 Subject: [PATCH 381/708] Introduce AutoGenerator subclass for frame based replay generation --- osu.Game/Rulesets/Replays/AutoGenerator.cs | 27 +++++------- .../Rulesets/Replays/FramedAutoGenerator.cs | 44 +++++++++++++++++++ 2 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Rulesets/Replays/FramedAutoGenerator.cs diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index b3c609f2f4..4bdd5b094d 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; @@ -7,34 +7,27 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Replays { - public abstract class AutoGenerator : IAutoGenerator + public abstract class AutoGenerator { /// - /// Creates the auto replay and returns it. - /// Every subclass of OsuAutoGeneratorBase should implement this! + /// The default duration of a key press in milliseconds. /// - public abstract Replay Generate(); - - #region Parameters + public const double KEY_UP_DELAY = 50; /// - /// The beatmap we're making. + /// The beatmap the autoplay is generated for. /// - protected IBeatmap Beatmap; - - #endregion + protected IBeatmap Beatmap { get; } protected AutoGenerator(IBeatmap beatmap) { Beatmap = beatmap; } - #region Constants - - // Shared amongst all modes - public const double KEY_UP_DELAY = 50; - - #endregion + /// + /// Generate the replay of the autoplay. + /// + public abstract Replay Generate(); protected virtual HitObject GetNextObject(int currentIndex) { diff --git a/osu.Game/Rulesets/Replays/FramedAutoGenerator.cs b/osu.Game/Rulesets/Replays/FramedAutoGenerator.cs new file mode 100644 index 0000000000..091eb00232 --- /dev/null +++ b/osu.Game/Rulesets/Replays/FramedAutoGenerator.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Replays; + +namespace osu.Game.Rulesets.Replays +{ + public abstract class FramedAutoGenerator : AutoGenerator + where TFrame : ReplayFrame + { + /// + /// The replay frames of the autoplay. + /// + protected readonly List Frames = new List(); + + protected TFrame? LastFrame => Frames.Count == 0 ? null : Frames[^1]; + + protected FramedAutoGenerator(IBeatmap beatmap) + : base(beatmap) + { + } + + public sealed override Replay Generate() + { + Frames.Clear(); + GenerateFrames(); + + return new Replay + { + Frames = Frames.OrderBy(frame => frame.Time).Cast().ToList() + }; + } + + /// + /// Generate the replay frames of the autoplay and populate . + /// + protected abstract void GenerateFrames(); + } +} From ea35b72436d6c38a477d3ae3e8dfb11b15653248 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 6 May 2021 22:56:21 +0900 Subject: [PATCH 382/708] Remove unused IAutoGenerator interface --- osu.Game/Rulesets/Replays/IAutoGenerator.cs | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 osu.Game/Rulesets/Replays/IAutoGenerator.cs diff --git a/osu.Game/Rulesets/Replays/IAutoGenerator.cs b/osu.Game/Rulesets/Replays/IAutoGenerator.cs deleted file mode 100644 index b1905e2b6f..0000000000 --- a/osu.Game/Rulesets/Replays/IAutoGenerator.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Replays; - -namespace osu.Game.Rulesets.Replays -{ - public interface IAutoGenerator - { - Replay Generate(); - } -} From cf39178099c9178e5ef9136bfdabc6dbcdf5cc02 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 6 May 2021 21:53:34 +0900 Subject: [PATCH 383/708] Use FramedAutoGenerator in Taiko, Catch, Mania OsuAutoGenerator is not included in this change because it uses SortedList-like thing --- .../Replays/CatchAutoGenerator.cs | 20 ++++--------------- .../Replays/ManiaAutoGenerator.cs | 15 ++++---------- .../Replays/TaikoAutoGenerator.cs | 14 +++---------- 3 files changed, 11 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 10230b6b78..2e1e4f7e77 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Replays; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; @@ -13,26 +12,19 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Catch.Replays { - internal class CatchAutoGenerator : AutoGenerator + internal class CatchAutoGenerator : FramedAutoGenerator { - public const double RELEASE_DELAY = 20; - public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap; public CatchAutoGenerator(IBeatmap beatmap) : base(beatmap) { - Replay = new Replay(); } - protected Replay Replay; - - private CatchReplayFrame currentFrame; - - public override Replay Generate() + protected override void GenerateFrames() { if (Beatmap.HitObjects.Count == 0) - return Replay; + return; // todo: add support for HT DT const double dash_speed = Catcher.BASE_SPEED; @@ -119,15 +111,11 @@ namespace osu.Game.Rulesets.Catch.Replays } } } - - return Replay; } private void addFrame(double time, float? position = null, bool dashing = false) { - var last = currentFrame; - currentFrame = new CatchReplayFrame(time, position, dashing, last); - Replay.Frames.Add(currentFrame); + Frames.Add(new CatchReplayFrame(time, position, dashing, LastFrame)); } } } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index ada84dfac2..4fd105025c 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; @@ -11,7 +10,7 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Mania.Replays { - internal class ManiaAutoGenerator : AutoGenerator + internal class ManiaAutoGenerator : FramedAutoGenerator { public const double RELEASE_DELAY = 20; @@ -22,8 +21,6 @@ namespace osu.Game.Rulesets.Mania.Replays public ManiaAutoGenerator(ManiaBeatmap beatmap) : base(beatmap) { - Replay = new Replay(); - columnActions = new ManiaAction[Beatmap.TotalColumns]; var normalAction = ManiaAction.Key1; @@ -43,12 +40,10 @@ namespace osu.Game.Rulesets.Mania.Replays } } - protected Replay Replay; - - public override Replay Generate() + protected override void GenerateFrames() { if (Beatmap.HitObjects.Count == 0) - return Replay; + return; var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); @@ -70,10 +65,8 @@ namespace osu.Game.Rulesets.Mania.Replays } } - Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray())); + Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray())); } - - return Replay; } private IEnumerable generateActionPoints() diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index fa0134aa94..bd2b7338de 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -2,10 +2,8 @@ // 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.Replays; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Taiko.Beatmaps; @@ -13,7 +11,7 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Taiko.Replays { - public class TaikoAutoGenerator : AutoGenerator + public class TaikoAutoGenerator : FramedAutoGenerator { public new TaikoBeatmap Beatmap => (TaikoBeatmap)base.Beatmap; @@ -22,16 +20,12 @@ namespace osu.Game.Rulesets.Taiko.Replays public TaikoAutoGenerator(IBeatmap beatmap) : base(beatmap) { - Replay = new Replay(); } - protected Replay Replay; - protected List Frames => Replay.Frames; - - public override Replay Generate() + protected override void GenerateFrames() { if (Beatmap.HitObjects.Count == 0) - return Replay; + return; bool hitButton = true; @@ -128,8 +122,6 @@ namespace osu.Game.Rulesets.Taiko.Replays hitButton = !hitButton; } - - return Replay; } } } From 95c74c906a33084142a71bd16b58d4063f237bfb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 6 May 2021 21:57:03 +0900 Subject: [PATCH 384/708] Use FramedAutoGenerator in template projects --- .../Replays/EmptyFreeformAutoGenerator.cs | 12 ++---------- .../Replays/PippidonAutoGenerator.cs | 12 ++---------- .../Replays/EmptyScrollingAutoGenerator.cs | 12 ++---------- .../Replays/PippidonAutoGenerator.cs | 12 ++---------- 4 files changed, 8 insertions(+), 40 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs index 6d8d4215a2..5d61136f54 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs @@ -1,28 +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.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Replays; using osu.Game.Rulesets.EmptyFreeform.Objects; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.EmptyFreeform.Replays { - public class EmptyFreeformAutoGenerator : AutoGenerator + public class EmptyFreeformAutoGenerator : FramedAutoGenerator { - protected Replay Replay; - protected List Frames => Replay.Frames; - public new Beatmap Beatmap => (Beatmap)base.Beatmap; public EmptyFreeformAutoGenerator(IBeatmap beatmap) : base(beatmap) { - Replay = new Replay(); } - public override Replay Generate() + protected override void GenerateFrames() { Frames.Add(new EmptyFreeformReplayFrame()); @@ -35,8 +29,6 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays // todo: add required inputs and extra frames. }); } - - return Replay; } } } diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs index 9c54b82e38..f795d7ef21 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs @@ -1,28 +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.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Replays; using osu.Game.Rulesets.Pippidon.Objects; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Pippidon.Replays { - public class PippidonAutoGenerator : AutoGenerator + public class PippidonAutoGenerator : FramedAutoGenerator { - protected Replay Replay; - protected List Frames => Replay.Frames; - public new Beatmap Beatmap => (Beatmap)base.Beatmap; public PippidonAutoGenerator(IBeatmap beatmap) : base(beatmap) { - Replay = new Replay(); } - public override Replay Generate() + protected override void GenerateFrames() { Frames.Add(new PippidonReplayFrame()); @@ -34,8 +28,6 @@ namespace osu.Game.Rulesets.Pippidon.Replays Position = hitObject.Position, }); } - - return Replay; } } } diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs index 7923918842..ab27fd4dcd 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs @@ -1,28 +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.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Replays; using osu.Game.Rulesets.EmptyScrolling.Objects; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.EmptyScrolling.Replays { - public class EmptyScrollingAutoGenerator : AutoGenerator + public class EmptyScrollingAutoGenerator : FramedAutoGenerator { - protected Replay Replay; - protected List Frames => Replay.Frames; - public new Beatmap Beatmap => (Beatmap)base.Beatmap; public EmptyScrollingAutoGenerator(IBeatmap beatmap) : base(beatmap) { - Replay = new Replay(); } - public override Replay Generate() + protected override void GenerateFrames() { Frames.Add(new EmptyScrollingReplayFrame()); @@ -34,8 +28,6 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays // todo: add required inputs and extra frames. }); } - - return Replay; } } } diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs index bd99cdcdbd..df2cfb8731 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs @@ -2,29 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Replays; using osu.Game.Rulesets.Pippidon.Objects; using osu.Game.Rulesets.Pippidon.UI; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Pippidon.Replays { - public class PippidonAutoGenerator : AutoGenerator + public class PippidonAutoGenerator : FramedAutoGenerator { - protected Replay Replay; - protected List Frames => Replay.Frames; - public new Beatmap Beatmap => (Beatmap)base.Beatmap; public PippidonAutoGenerator(IBeatmap beatmap) : base(beatmap) { - Replay = new Replay(); } - public override Replay Generate() + protected override void GenerateFrames() { int currentLane = 0; @@ -55,8 +49,6 @@ namespace osu.Game.Rulesets.Pippidon.Replays currentLane = hitObject.Lane; } - - return Replay; } private void addFrame(double time, PippidonAction direction) From 207f7f1e563ce252eb88affc19cdd09544d80212 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 7 May 2021 00:31:12 +0900 Subject: [PATCH 385/708] Rename FramedAutoGenerator -> AutoGenerator --- .../Replays/EmptyFreeformAutoGenerator.cs | 2 +- .../Replays/PippidonAutoGenerator.cs | 2 +- .../Replays/EmptyScrollingAutoGenerator.cs | 2 +- .../Replays/PippidonAutoGenerator.cs | 2 +- .../Replays/CatchAutoGenerator.cs | 2 +- .../Replays/ManiaAutoGenerator.cs | 2 +- .../Replays/TaikoAutoGenerator.cs | 2 +- osu.Game/Rulesets/Replays/AutoGenerator.cs | 36 +++++++++++++++ .../Rulesets/Replays/FramedAutoGenerator.cs | 44 ------------------- 9 files changed, 43 insertions(+), 51 deletions(-) delete mode 100644 osu.Game/Rulesets/Replays/FramedAutoGenerator.cs diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs index 5d61136f54..62f394d1ce 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.EmptyFreeform.Replays { - public class EmptyFreeformAutoGenerator : FramedAutoGenerator + public class EmptyFreeformAutoGenerator : AutoGenerator { public new Beatmap Beatmap => (Beatmap)base.Beatmap; diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs index f795d7ef21..612288257d 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Pippidon.Replays { - public class PippidonAutoGenerator : FramedAutoGenerator + public class PippidonAutoGenerator : AutoGenerator { public new Beatmap Beatmap => (Beatmap)base.Beatmap; diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs index ab27fd4dcd..1058f756f3 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.EmptyScrolling.Replays { - public class EmptyScrollingAutoGenerator : FramedAutoGenerator + public class EmptyScrollingAutoGenerator : AutoGenerator { public new Beatmap Beatmap => (Beatmap)base.Beatmap; diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs index df2cfb8731..724026273d 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Pippidon.Replays { - public class PippidonAutoGenerator : FramedAutoGenerator + public class PippidonAutoGenerator : AutoGenerator { public new Beatmap Beatmap => (Beatmap)base.Beatmap; diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 2e1e4f7e77..a81703119a 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Catch.Replays { - internal class CatchAutoGenerator : FramedAutoGenerator + internal class CatchAutoGenerator : AutoGenerator { public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 4fd105025c..517b708691 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Mania.Replays { - internal class ManiaAutoGenerator : FramedAutoGenerator + internal class ManiaAutoGenerator : AutoGenerator { public const double RELEASE_DELAY = 20; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index bd2b7338de..5fd281f9fa 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Taiko.Replays { - public class TaikoAutoGenerator : FramedAutoGenerator + public class TaikoAutoGenerator : AutoGenerator { public new TaikoBeatmap Beatmap => (TaikoBeatmap)base.Beatmap; diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index 4bdd5b094d..6ce857b14b 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Replays; using osu.Game.Rulesets.Objects; @@ -37,4 +40,37 @@ namespace osu.Game.Rulesets.Replays return Beatmap.HitObjects[currentIndex + 1]; } } + + public abstract class AutoGenerator : AutoGenerator + where TFrame : ReplayFrame + { + /// + /// The replay frames of the autoplay. + /// + protected readonly List Frames = new List(); + + [CanBeNull] + protected TFrame LastFrame => Frames.Count == 0 ? null : Frames[^1]; + + protected AutoGenerator(IBeatmap beatmap) + : base(beatmap) + { + } + + public sealed override Replay Generate() + { + Frames.Clear(); + GenerateFrames(); + + return new Replay + { + Frames = Frames.OrderBy(frame => frame.Time).Cast().ToList() + }; + } + + /// + /// Generate the replay frames of the autoplay and populate . + /// + protected abstract void GenerateFrames(); + } } diff --git a/osu.Game/Rulesets/Replays/FramedAutoGenerator.cs b/osu.Game/Rulesets/Replays/FramedAutoGenerator.cs deleted file mode 100644 index 091eb00232..0000000000 --- a/osu.Game/Rulesets/Replays/FramedAutoGenerator.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Replays; - -namespace osu.Game.Rulesets.Replays -{ - public abstract class FramedAutoGenerator : AutoGenerator - where TFrame : ReplayFrame - { - /// - /// The replay frames of the autoplay. - /// - protected readonly List Frames = new List(); - - protected TFrame? LastFrame => Frames.Count == 0 ? null : Frames[^1]; - - protected FramedAutoGenerator(IBeatmap beatmap) - : base(beatmap) - { - } - - public sealed override Replay Generate() - { - Frames.Clear(); - GenerateFrames(); - - return new Replay - { - Frames = Frames.OrderBy(frame => frame.Time).Cast().ToList() - }; - } - - /// - /// Generate the replay frames of the autoplay and populate . - /// - protected abstract void GenerateFrames(); - } -} From 54fe10c82a855a16c88f8781a9ae12dc42df7b0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 10:36:03 +0900 Subject: [PATCH 386/708] Refactor `SliderSelectionBlueprint` to not reference blueprint pieces for input handling --- .../Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs | 6 ------ .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 6e22c35ab3..ece9c7a757 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -26,10 +26,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { AccentColour = Color4.Transparent }; - - // SliderSelectionBlueprint relies on calling ReceivePositionalInputAt on this drawable to determine whether selection should occur. - // Without AlwaysPresent, a movement in a parent container (ie. the editor composer area resizing) could cause incorrect input handling. - AlwaysPresent = true; } [BackgroundDependencyLoader] @@ -54,7 +50,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } public void RecyclePath() => body.RecyclePath(); - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 88fcb1e715..32d2fba25e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -240,10 +240,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), }; - public override Vector2 ScreenSpaceSelectionPoint => BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); + public override Vector2 ScreenSpaceSelectionPoint => this.ToScreenSpace(slider.HitObject.StackedPosition); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => - BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; + slider.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); } From 84da24700204724cde9a8b90506b078add49c964 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 May 2021 05:32:55 +0300 Subject: [PATCH 387/708] Fix editor clock using the wrong beatmap track on creation --- osu.Game/Screens/Edit/Editor.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index da0e9ebbaf..138d8cfaff 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -145,7 +145,7 @@ namespace osu.Game.Screens.Edit // Todo: should probably be done at a DrawableRuleset level to share logic with Player. clock = new EditorClock(playableBeatmap, beatDivisor) { IsCoupled = false }; - UpdateClockSource(); + updateClockSource(loadableBeatmap); dependencies.CacheAs(clock); AddInternal(clock); @@ -308,9 +308,11 @@ namespace osu.Game.Screens.Edit /// /// If the beatmap's track has changed, this method must be called to keep the editor in a valid state. /// - public void UpdateClockSource() + public void UpdateClockSource() => updateClockSource(Beatmap.Value); + + private void updateClockSource(WorkingBeatmap beatmap) { - var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); + var sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock(); clock.ChangeSource(sourceClock); } From 539643c72b390b1c40b8fb8340b37f33ef91d7d5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 May 2021 07:18:43 +0300 Subject: [PATCH 388/708] Set loadable beatmap track to clock immediately in BDL This reverts commit 84da24700204724cde9a8b90506b078add49c964. Use loadable beatmap track for clock directly in BDL --- osu.Game/Screens/Edit/Editor.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 138d8cfaff..a4c331c4e0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -144,8 +144,7 @@ namespace osu.Game.Screens.Edit // Todo: should probably be done at a DrawableRuleset level to share logic with Player. clock = new EditorClock(playableBeatmap, beatDivisor) { IsCoupled = false }; - - updateClockSource(loadableBeatmap); + clock.ChangeSource(loadableBeatmap.Track); dependencies.CacheAs(clock); AddInternal(clock); @@ -308,11 +307,9 @@ namespace osu.Game.Screens.Edit /// /// If the beatmap's track has changed, this method must be called to keep the editor in a valid state. /// - public void UpdateClockSource() => updateClockSource(Beatmap.Value); - - private void updateClockSource(WorkingBeatmap beatmap) + public void UpdateClockSource() { - var sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock(); + var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); clock.ChangeSource(sourceClock); } From f9d99a98826525b58d8a076f772da6a9a77329ec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 May 2021 07:25:14 +0300 Subject: [PATCH 389/708] Mark `WorkingBeatmap.Track` as not null --- osu.Game/Beatmaps/WorkingBeatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index e0eeaf6db0..bef258753e 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -298,6 +298,7 @@ namespace osu.Game.Beatmaps /// Get the loaded audio track instance. must have first been called. /// This generally happens via MusicController when changing the global beatmap. /// + [NotNull] public Track Track { get From 71547bece0dcd99cfbe6ba95bd2e42066b6eb686 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 May 2021 07:43:06 +0300 Subject: [PATCH 390/708] Remove any null-coalescing/conditionals in `WorkingBeatmap.Track` usages --- osu.Game/Screens/Edit/Editor.cs | 9 ++------- osu.Game/Tests/Visual/EditorClockTestScene.cs | 3 +-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a4c331c4e0..434683a016 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -17,7 +17,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -307,11 +306,7 @@ namespace osu.Game.Screens.Edit /// /// If the beatmap's track has changed, this method must be called to keep the editor in a valid state. /// - public void UpdateClockSource() - { - var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); - clock.ChangeSource(sourceClock); - } + public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track); protected void Save() { @@ -582,7 +577,7 @@ namespace osu.Game.Screens.Edit private void resetTrack(bool seekToStart = false) { - Beatmap.Value.Track?.Stop(); + Beatmap.Value.Track.Stop(); if (seekToStart) { diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 79cfee8518..34393fba7d 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; @@ -46,7 +45,7 @@ namespace osu.Game.Tests.Visual private void beatmapChanged(ValueChangedEvent e) { Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo; - Clock.ChangeSource((IAdjustableClock)e.NewValue.Track ?? new StopwatchClock()); + Clock.ChangeSource(e.NewValue.Track); Clock.ProcessFrame(); } From 37f44d2e37602ec557ed7fda6ca7afced0bfec81 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 May 2021 08:27:56 +0300 Subject: [PATCH 391/708] Revert wrong not-null track changes This reverts commit f9d99a98826525b58d8a076f772da6a9a77329ec. This reverts commit 71547bece0dcd99cfbe6ba95bd2e42066b6eb686. --- osu.Game/Beatmaps/WorkingBeatmap.cs | 1 - osu.Game/Screens/Edit/Editor.cs | 9 +++++++-- osu.Game/Tests/Visual/EditorClockTestScene.cs | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index bef258753e..e0eeaf6db0 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -298,7 +298,6 @@ namespace osu.Game.Beatmaps /// Get the loaded audio track instance. must have first been called. /// This generally happens via MusicController when changing the global beatmap. /// - [NotNull] public Track Track { get diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 434683a016..a4c331c4e0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -17,6 +17,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -306,7 +307,11 @@ namespace osu.Game.Screens.Edit /// /// If the beatmap's track has changed, this method must be called to keep the editor in a valid state. /// - public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track); + public void UpdateClockSource() + { + var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); + clock.ChangeSource(sourceClock); + } protected void Save() { @@ -577,7 +582,7 @@ namespace osu.Game.Screens.Edit private void resetTrack(bool seekToStart = false) { - Beatmap.Value.Track.Stop(); + Beatmap.Value.Track?.Stop(); if (seekToStart) { diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 34393fba7d..79cfee8518 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Events; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; @@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual private void beatmapChanged(ValueChangedEvent e) { Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo; - Clock.ChangeSource(e.NewValue.Track); + Clock.ChangeSource((IAdjustableClock)e.NewValue.Track ?? new StopwatchClock()); Clock.ProcessFrame(); } From b1134c3857980e5f473dc3ba3f57d1ab7bdcabdb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 May 2021 08:30:50 +0300 Subject: [PATCH 392/708] Guard against potentially null track if ever --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a4c331c4e0..78d5c24108 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -144,7 +144,7 @@ namespace osu.Game.Screens.Edit // Todo: should probably be done at a DrawableRuleset level to share logic with Player. clock = new EditorClock(playableBeatmap, beatDivisor) { IsCoupled = false }; - clock.ChangeSource(loadableBeatmap.Track); + clock.ChangeSource((IAdjustableClock)loadableBeatmap.Track ?? new StopwatchClock()); dependencies.CacheAs(clock); AddInternal(clock); From bdfe44ddca7235cd7a42bac8015f2e55b13bf905 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 13:19:30 +0700 Subject: [PATCH 393/708] change OsuMarkdownListItem to abstract class --- .../Markdown/OsuMarkdownListItem.cs | 72 +++---------------- 1 file changed, 10 insertions(+), 62 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs index b586bb7f30..8c4c3e1da2 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownListItem.cs @@ -1,41 +1,34 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Markdig.Syntax; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; using osuTK; namespace osu.Game.Graphics.Containers.Markdown { - public class OsuMarkdownListItem : CompositeDrawable + public abstract class OsuMarkdownListItem : CompositeDrawable { - private const float ordered_left_padding = 30; - private const float unordered_left_padding = 20; - - private readonly int level; - private readonly int order; - private readonly bool isOrdered; - [Resolved] private IMarkdownTextComponent parentTextComponent { get; set; } - public FillFlowContainer Content { get; } + public FillFlowContainer Content { get; private set; } - public OsuMarkdownListItem(ListItemBlock listItemBlock, int level) + protected OsuMarkdownListItem() { - this.level = level; - order = listItemBlock.Order; - isOrdered = ((ListBlock)listItemBlock.Parent).IsOrdered; - AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - Padding = new MarginPadding { Left = isOrdered ? ordered_left_padding : unordered_left_padding }; + } + [BackgroundDependencyLoader] + private void load() + { InternalChildren = new Drawable[] { + CreateMarker(), Content = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -46,51 +39,6 @@ namespace osu.Game.Graphics.Containers.Markdown }; } - [BackgroundDependencyLoader] - private void load() - { - var marker = parentTextComponent.CreateSpriteText(); - marker.Text = GetTextMarker(level, isOrdered); - - if (isOrdered) - { - marker.X = -ordered_left_padding; - } - else - { - marker.Font = OsuFont.GetFont(size: marker.Font.Size / 2); - marker.Origin = Anchor.Centre; - marker.X = -unordered_left_padding / 2; - marker.Y = marker.Font.Size; - } - - AddInternal(marker); - } - - /// - /// Get text marker based on and . - /// - /// The markdown level of current list item. - /// Is true if the list item is an ordered list. - /// - protected virtual string GetTextMarker(int level, bool isOrdered) - { - if (isOrdered) - { - return $"{order}."; - } - - switch (level) - { - case 1: - return "●"; - - case 2: - return "○"; - - default: - return "■"; - } - } + protected virtual SpriteText CreateMarker() => parentTextComponent.CreateSpriteText(); } } From dfcf760b7be0592eb4212e3fdc9bc21073eaaa59 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 13:20:06 +0700 Subject: [PATCH 394/708] add OsuMarkdownOrderedListItem --- .../Markdown/OsuMarkdownOrderedListItem.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownOrderedListItem.cs diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownOrderedListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownOrderedListItem.cs new file mode 100644 index 0000000000..8fedb189b2 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownOrderedListItem.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownOrderedListItem : OsuMarkdownListItem + { + private const float left_padding = 30; + + private readonly int order; + + public OsuMarkdownOrderedListItem(int order) + { + this.order = order; + Padding = new MarginPadding { Left = left_padding }; + } + + protected override SpriteText CreateMarker() => base.CreateMarker().With(t => + { + t.X = -left_padding; + t.Text = $"{order}."; + }); + } +} From 9233248a0ba84919802e8dbae203a5a95b2fceaa Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 13:20:20 +0700 Subject: [PATCH 395/708] add OsuMarkdownUnorderedListItem --- .../Markdown/OsuMarkdownUnorderedListItem.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownUnorderedListItem.cs diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownUnorderedListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownUnorderedListItem.cs new file mode 100644 index 0000000000..8bfaf8ad21 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownUnorderedListItem.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics.Containers.Markdown +{ + public class OsuMarkdownUnorderedListItem : OsuMarkdownListItem + { + private const float left_padding = 20; + + private readonly int level; + + public OsuMarkdownUnorderedListItem(int level) + { + this.level = level; + + Padding = new MarginPadding { Left = left_padding }; + } + + protected override SpriteText CreateMarker() => base.CreateMarker().With(t => + { + t.Text = GetTextMarker(level); + t.Font = t.Font.With(size: t.Font.Size / 2); + t.Origin = Anchor.Centre; + t.X = -left_padding / 2; + t.Y = t.Font.Size; + }); + + /// + /// Get text marker based on + /// + /// The markdown level of current list item. + /// + protected virtual string GetTextMarker(int level) + { + switch (level) + { + case 1: + return "●"; + + case 2: + return "○"; + + default: + return "■"; + } + } + } +} From 5b003750f87044d16288b7b992023cc8011bf832 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 13:20:48 +0700 Subject: [PATCH 396/708] change CreateListItem method in OsuMarkdownContainer --- .../Containers/Markdown/OsuMarkdownContainer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index c66f3fbca2..6facf4e26c 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -30,7 +30,8 @@ namespace osu.Game.Graphics.Containers.Markdown break; case ListItemBlock listItemBlock: - var childContainer = CreateListItem(listItemBlock, level); + var isOrdered = ((ListBlock)listItemBlock.Parent).IsOrdered; + var childContainer = CreateListItem(listItemBlock, level, isOrdered); container.Add(childContainer); foreach (var single in listItemBlock) base.AddMarkdownComponent(single, childContainer.Content, level); @@ -64,7 +65,13 @@ namespace osu.Game.Graphics.Containers.Markdown Padding = new MarginPadding(0) }; - protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock, int level) => new OsuMarkdownListItem(listItemBlock, level); + protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock, int level, bool isOrdered) + { + if (isOrdered) + return new OsuMarkdownOrderedListItem(listItemBlock.Order); + + return new OsuMarkdownUnorderedListItem(level); + } protected override MarkdownPipeline CreateBuilder() => new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub) From 7c4e54a1d43990996e015affd03911514dc18952 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 May 2021 09:53:54 +0300 Subject: [PATCH 397/708] Unrevert null-colaescing/conditionals removal --- osu.Game/Screens/Edit/Editor.cs | 8 ++------ osu.Game/Tests/Visual/EditorClockTestScene.cs | 3 +-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 78d5c24108..50dcb84235 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -307,11 +307,7 @@ namespace osu.Game.Screens.Edit /// /// If the beatmap's track has changed, this method must be called to keep the editor in a valid state. /// - public void UpdateClockSource() - { - var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); - clock.ChangeSource(sourceClock); - } + public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track); protected void Save() { @@ -582,7 +578,7 @@ namespace osu.Game.Screens.Edit private void resetTrack(bool seekToStart = false) { - Beatmap.Value.Track?.Stop(); + Beatmap.Value.Track.Stop(); if (seekToStart) { diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 79cfee8518..34393fba7d 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; @@ -46,7 +45,7 @@ namespace osu.Game.Tests.Visual private void beatmapChanged(ValueChangedEvent e) { Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo; - Clock.ChangeSource((IAdjustableClock)e.NewValue.Track ?? new StopwatchClock()); + Clock.ChangeSource(e.NewValue.Track); Clock.ProcessFrame(); } From fc2a527e9d419040697cd4e075c7ae04d1cd2c82 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 May 2021 09:54:58 +0300 Subject: [PATCH 398/708] Revert "Guard against potentially null track if ever" This reverts commit b1134c3857980e5f473dc3ba3f57d1ab7bdcabdb. --- osu.Game/Screens/Edit/Editor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 50dcb84235..434683a016 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -17,7 +17,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -144,7 +143,7 @@ namespace osu.Game.Screens.Edit // Todo: should probably be done at a DrawableRuleset level to share logic with Player. clock = new EditorClock(playableBeatmap, beatDivisor) { IsCoupled = false }; - clock.ChangeSource((IAdjustableClock)loadableBeatmap.Track ?? new StopwatchClock()); + clock.ChangeSource(loadableBeatmap.Track); dependencies.CacheAs(clock); AddInternal(clock); From a2e4fb5b6b0cc7cd97fa439a9e2e1b13dcb6f10e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 16:10:57 +0900 Subject: [PATCH 399/708] Update `ScoreCounter` components to bind outwards --- .../TestSceneSkinnableScoreCounter.cs | 26 ++++------ .../Graphics/UserInterface/ScoreCounter.cs | 3 +- .../Screens/Play/HUD/DefaultScoreCounter.cs | 3 +- .../Screens/Play/HUD/GameplayScoreCounter.cs | 46 ++++++++++++++++++ osu.Game/Screens/Play/HUD/IScoreCounter.cs | 25 ---------- .../Screens/Play/HUD/SkinnableScoreCounter.cs | 47 +------------------ osu.Game/Screens/Play/HUDOverlay.cs | 1 - osu.Game/Skinning/LegacyScoreCounter.cs | 4 +- 8 files changed, 60 insertions(+), 95 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/GameplayScoreCounter.cs delete mode 100644 osu.Game/Screens/Play/HUD/IScoreCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs index e212ceeba7..4f2183711e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs @@ -4,10 +4,12 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay @@ -18,37 +20,27 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(); + [SetUpSteps] public void SetUpSteps() { - AddStep("Create combo counters", () => SetContents(() => - { - var comboCounter = new SkinnableScoreCounter(); - comboCounter.Current.Value = 1; - return comboCounter; - })); + AddStep("Create score counters", () => SetContents(() => new SkinnableScoreCounter())); } [Test] public void TestScoreCounterIncrementing() { - AddStep(@"Reset all", delegate - { - foreach (var s in scoreCounters) - s.Current.Value = 0; - }); + AddStep(@"Reset all", () => scoreProcessor.TotalScore.Value = 0); - AddStep(@"Hit! :D", delegate - { - foreach (var s in scoreCounters) - s.Current.Value += 300; - }); + AddStep(@"Hit! :D", () => scoreProcessor.TotalScore.Value += 300); } [Test] public void TestVeryLargeScore() { - AddStep("set large score", () => scoreCounters.ForEach(counter => counter.Current.Value = 1_000_000_000)); + AddStep("set large score", () => scoreCounters.ForEach(counter => scoreProcessor.TotalScore.Value = 1_000_000_000)); } } } diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index d75e49a4ce..5747c846eb 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -4,11 +4,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Play.HUD; namespace osu.Game.Graphics.UserInterface { - public abstract class ScoreCounter : RollingCounter, IScoreCounter + public abstract class ScoreCounter : RollingCounter { protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index 1dcfe2e067..84db605d53 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -4,11 +4,10 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Play.HUD { - public class DefaultScoreCounter : ScoreCounter + public class DefaultScoreCounter : GameplayScoreCounter { public DefaultScoreCounter() : base(6) diff --git a/osu.Game/Screens/Play/HUD/GameplayScoreCounter.cs b/osu.Game/Screens/Play/HUD/GameplayScoreCounter.cs new file mode 100644 index 0000000000..e09630d2c4 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/GameplayScoreCounter.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Screens.Play.HUD +{ + public abstract class GameplayScoreCounter : ScoreCounter + { + private Bindable scoreDisplayMode; + + protected GameplayScoreCounter(int leading = 0, bool useCommaSeparator = false) + : base(leading, useCommaSeparator) + { + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, ScoreProcessor scoreProcessor) + { + scoreDisplayMode = config.GetBindable(OsuSetting.ScoreDisplayMode); + scoreDisplayMode.BindValueChanged(scoreMode => + { + switch (scoreMode.NewValue) + { + case ScoringMode.Standardised: + RequiredDisplayDigits.Value = 6; + break; + + case ScoringMode.Classic: + RequiredDisplayDigits.Value = 8; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(scoreMode)); + } + }, true); + + Current.BindTo(scoreProcessor.TotalScore); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/IScoreCounter.cs b/osu.Game/Screens/Play/HUD/IScoreCounter.cs deleted file mode 100644 index 7f5e81d5ef..0000000000 --- a/osu.Game/Screens/Play/HUD/IScoreCounter.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Bindables; -using osu.Framework.Graphics; - -namespace osu.Game.Screens.Play.HUD -{ - /// - /// An interface providing a set of methods to update a score counter. - /// - public interface IScoreCounter : IDrawable - { - /// - /// The current score to be displayed. - /// - Bindable Current { get; } - - /// - /// The number of digits required to display most sane scores. - /// This may be exceeded in very rare cases, but is useful to pad or space the display to avoid it jumping around. - /// - Bindable RequiredDisplayDigits { get; } - } -} diff --git a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs index b46f5684b1..cc9a712e97 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs @@ -1,61 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Configuration; -using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableScoreCounter : SkinnableDrawable, IScoreCounter + public class SkinnableScoreCounter : SkinnableDrawable { - public Bindable Current { get; } = new Bindable(); - - private Bindable scoreDisplayMode; - - public Bindable RequiredDisplayDigits { get; } = new Bindable(); - public SkinnableScoreCounter() : base(new HUDSkinComponent(HUDSkinComponents.ScoreCounter), _ => new DefaultScoreCounter()) { CentreComponent = false; } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - scoreDisplayMode = config.GetBindable(OsuSetting.ScoreDisplayMode); - scoreDisplayMode.BindValueChanged(scoreMode => - { - switch (scoreMode.NewValue) - { - case ScoringMode.Standardised: - RequiredDisplayDigits.Value = 6; - break; - - case ScoringMode.Classic: - RequiredDisplayDigits.Value = 8; - break; - - default: - throw new ArgumentOutOfRangeException(nameof(scoreMode)); - } - }, true); - } - - private IScoreCounter skinnedCounter; - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - skinnedCounter = Drawable as IScoreCounter; - - skinnedCounter?.Current.BindTo(Current); - skinnedCounter?.RequiredDisplayDigits.BindTo(RequiredDisplayDigits); - } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 83897c5167..c887fb78e0 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -320,7 +320,6 @@ namespace osu.Game.Screens.Play protected virtual void BindScoreProcessor(ScoreProcessor processor) { - ScoreCounter?.Current.BindTo(processor.TotalScore); AccuracyCounter?.Current.BindTo(processor.Accuracy); if (HealthDisplay is IHealthDisplay shd) diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 1d330ef495..e385caf304 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -4,12 +4,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 : GameplayScoreCounter { private readonly ISkin skin; From ad398165a2d38870173df9b82034691d8c9c8d4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 16:26:54 +0900 Subject: [PATCH 400/708] Update `AccuracyCounter` components to use DI to attach data source --- .../TestSceneSkinnableAccuracyCounter.cs | 26 ++++++------------- .../Play/HUD/DefaultAccuracyCounter.cs | 3 +-- .../Play/HUD/GameplayAccuracyCounter.cs | 18 +++++++++++++ osu.Game/Screens/Play/HUD/IAccuracyCounter.cs | 19 -------------- .../Play/HUD/SkinnableAccuracyCounter.cs | 12 +-------- osu.Game/Skinning/LegacyAccuracyCounter.cs | 3 +-- 6 files changed, 29 insertions(+), 52 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs delete mode 100644 osu.Game/Screens/Play/HUD/IAccuracyCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs index 709929dcb0..34356184a2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs @@ -4,9 +4,11 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay @@ -17,33 +19,21 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(); + [SetUpSteps] public void SetUpSteps() { - AddStep("Create combo counters", () => SetContents(() => - { - var accuracyCounter = new SkinnableAccuracyCounter(); - - accuracyCounter.Current.Value = 1; - - return accuracyCounter; - })); + AddStep("Create combo counters", () => SetContents(() => new SkinnableAccuracyCounter())); } [Test] public void TestChangingAccuracy() { - AddStep(@"Reset all", delegate - { - foreach (var s in accuracyCounters) - s.Current.Value = 1; - }); + AddStep(@"Reset all", () => scoreProcessor.Accuracy.Value = 1); - AddStep(@"Hit! :D", delegate - { - foreach (var s in accuracyCounters) - s.Current.Value -= 0.023f; - }); + AddStep(@"Hit! :D", () => scoreProcessor.Accuracy.Value -= 0.23); } } } diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index d5d8ec570a..be1a4ad6b3 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -4,12 +4,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Screens.Play.HUD { - public class DefaultAccuracyCounter : PercentageCounter, IAccuracyCounter + public class DefaultAccuracyCounter : GameplayAccuracyCounter { private readonly Vector2 offset = new Vector2(-20, 5); diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs new file mode 100644 index 0000000000..7a63084812 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Screens.Play.HUD +{ + public abstract class GameplayAccuracyCounter : PercentageCounter + { + [BackgroundDependencyLoader] + private void load(ScoreProcessor scoreProcessor) + { + Current.BindTo(scoreProcessor.Accuracy); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/IAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/IAccuracyCounter.cs deleted file mode 100644 index 0199250a08..0000000000 --- a/osu.Game/Screens/Play/HUD/IAccuracyCounter.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Bindables; -using osu.Framework.Graphics; - -namespace osu.Game.Screens.Play.HUD -{ - /// - /// An interface providing a set of methods to update a accuracy counter. - /// - public interface IAccuracyCounter : IDrawable - { - /// - /// The current accuracy to be displayed. - /// - Bindable Current { get; } - } -} diff --git a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs index 76c9c30813..17c2493d2d 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 : SkinnableDrawable { public Bindable Current { get; } = new Bindable(); @@ -15,15 +15,5 @@ namespace osu.Game.Screens.Play.HUD { CentreComponent = false; } - - private IAccuracyCounter skinnedCounter; - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - skinnedCounter = Drawable as IAccuracyCounter; - skinnedCounter?.Current.BindTo(Current); - } } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 7d6f1dc916..638faa8f2b 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -4,14 +4,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Skinning { - public class LegacyAccuracyCounter : PercentageCounter, IAccuracyCounter + public class LegacyAccuracyCounter : GameplayAccuracyCounter { private readonly ISkin skin; From 3524cb792444dd1f400a8e54bb0d4ce425d48b4f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 14:36:35 +0700 Subject: [PATCH 401/708] simplify CreateSpriteText in markdown heading --- .../Graphics/Containers/Markdown/OsuMarkdownHeading.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs index a7fecc6e25..40eb4cad15 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownHeading.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using Markdig.Syntax; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Sprites; @@ -68,12 +69,7 @@ namespace osu.Game.Graphics.Containers.Markdown { public FontWeight Weight { get; set; } - protected override SpriteText CreateSpriteText() - { - var spriteText = base.CreateSpriteText(); - spriteText.Font = spriteText.Font.With(weight: Weight); - return spriteText; - } + protected override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: Weight)); } } } From 17b8963cf8db3f908359905bded8907f49398503 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 14:38:19 +0700 Subject: [PATCH 402/708] simplify CreateSpriteText in markdown table cell --- .../Graphics/Containers/Markdown/OsuMarkdownTableCell.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs index fca85e02a8..763c9d7536 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs @@ -59,12 +59,7 @@ namespace osu.Game.Graphics.Containers.Markdown { public FontWeight Weight { get; set; } - protected override SpriteText CreateSpriteText() - { - var spriteText = base.CreateSpriteText(); - spriteText.Font = spriteText.Font.With(weight: Weight); - return spriteText; - } + protected override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: Weight)); } } } From 79a1d7b2b39d018b231abc2508ab1c1494acd39d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 14:40:01 +0700 Subject: [PATCH 403/708] simplify CreateEmphasisedSpriteText --- .../Containers/Markdown/OsuMarkdownTextFlowContainer.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 517143c2db..1550b8401d 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -3,6 +3,7 @@ using Markdig.Syntax.Inlines; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; @@ -28,10 +29,6 @@ namespace osu.Game.Graphics.Containers.Markdown => AddText(codeInline.Content, t => { t.Colour = colourProvider.Light1; }); protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic) - { - var spriteText = CreateSpriteText(); - spriteText.Font = spriteText.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic); - return spriteText; - } + => CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic)); } } From 64e9c5e9ba50924903ec6e280b68552d878e00f5 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 14:41:27 +0700 Subject: [PATCH 404/708] add return xmldoc in markdown unordered list --- .../Containers/Markdown/OsuMarkdownUnorderedListItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownUnorderedListItem.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownUnorderedListItem.cs index 8bfaf8ad21..5d1e114781 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownUnorderedListItem.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownUnorderedListItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Graphics.Containers.Markdown /// Get text marker based on /// /// The markdown level of current list item. - /// + /// The marker string of this list item protected virtual string GetTextMarker(int level) { switch (level) From d92e593ddddd425c4984e25346581af3d6da50f1 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 14:47:46 +0700 Subject: [PATCH 405/708] extract out table head and body border into separate component --- .../Markdown/OsuMarkdownTableCell.cs | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs index 763c9d7536..5949bf074b 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs @@ -26,27 +26,10 @@ namespace osu.Game.Graphics.Containers.Markdown [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - var border = new Box - { - RelativeSizeAxes = Axes.X, - }; - if (isHeading) - { - border.Colour = colourProvider.Background3; - border.Height = 2; - border.Anchor = Anchor.BottomLeft; - border.Origin = Anchor.BottomLeft; - } + AddInternal(new TableHeadBorder()); else - { - border.Colour = colourProvider.Background4; - border.Height = 1; - border.Anchor = Anchor.TopLeft; - border.Origin = Anchor.TopLeft; - } - - AddInternal(border); + AddInternal(new TableBodyBorder()); } public override MarkdownTextFlowContainer CreateTextFlow() => new TableCellTextFlowContainer @@ -55,6 +38,30 @@ namespace osu.Game.Graphics.Containers.Markdown Padding = new MarginPadding(10), }; + private class TableHeadBorder : Box + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Colour = colourProvider.Background3; + RelativeSizeAxes = Axes.X; + Height = 2; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + } + } + + private class TableBodyBorder : Box + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Colour = colourProvider.Background4; + RelativeSizeAxes = Axes.X; + Height = 1; + } + } + private class TableCellTextFlowContainer : OsuMarkdownTextFlowContainer { public FontWeight Weight { get; set; } From 22677cfeaf7aea4657a1040d80eecdf12b3b1746 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 14:54:46 +0700 Subject: [PATCH 406/708] add CreateBorder method in markdown table cell --- .../Containers/Markdown/OsuMarkdownTableCell.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs index 5949bf074b..ac7d07e283 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTableCell.cs @@ -24,12 +24,9 @@ namespace osu.Game.Graphics.Containers.Markdown } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load() { - if (isHeading) - AddInternal(new TableHeadBorder()); - else - AddInternal(new TableBodyBorder()); + AddInternal(CreateBorder(isHeading)); } public override MarkdownTextFlowContainer CreateTextFlow() => new TableCellTextFlowContainer @@ -38,6 +35,14 @@ namespace osu.Game.Graphics.Containers.Markdown Padding = new MarginPadding(10), }; + protected virtual Box CreateBorder(bool isHeading) + { + if (isHeading) + return new TableHeadBorder(); + + return new TableBodyBorder(); + } + private class TableHeadBorder : Box { [BackgroundDependencyLoader] From 9d27b11e499e634cf3276b1023bb8ac9db9cdf18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 17:10:31 +0900 Subject: [PATCH 407/708] Update skin editor test scene to cache a `ScoreProcessor` --- .../Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 086bcb19c3..2424d24bc6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -17,6 +18,9 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinEditorMultipleSkins : SkinnableTestScene { + [Cached] + private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(); + [SetUpSteps] public void SetUpSteps() { @@ -28,8 +32,6 @@ namespace osu.Game.Tests.Visual.Gameplay 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()) @@ -40,7 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay // Add any key just to display the key counter visually. hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); - hudOverlay.ComboCounter.Current.Value = 1; + scoreProcessor.Combo.Value = 1; return new Container { From 755588258e90883f22a89cee219d6e44373b5887 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 16:56:24 +0900 Subject: [PATCH 408/708] Update `HealthDisplay` components to use DI to attach data source --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 35 ++++++++++++++++--- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 5 ++- .../TestSceneSkinEditorMultipleSkins.cs | 3 +- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 2 +- .../TestSceneSkinnableHealthDisplay.cs | 21 ++++++----- .../Screens/Play/HUD/DefaultHealthDisplay.cs | 2 +- osu.Game/Screens/Play/HUD/FailingLayer.cs | 11 +----- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 33 ++++++++++++----- osu.Game/Screens/Play/HUD/IHealthDisplay.cs | 26 -------------- .../Play/HUD/SkinnableHealthDisplay.cs | 15 +------- osu.Game/Screens/Play/HUDOverlay.cs | 22 +----------- osu.Game/Screens/Play/Player.cs | 4 ++- osu.Game/Skinning/LegacyHealthDisplay.cs | 12 ++----- 13 files changed, 81 insertions(+), 110 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/IHealthDisplay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 5a1a9d3d87..e426726def 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; @@ -22,22 +23,30 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUpSteps] public void SetUpSteps() + { + } + + private void create(HealthProcessor healthProcessor) { AddStep("create layer", () => { - Child = layer = new FailingLayer(); - layer.BindHealthProcessor(new DrainingHealthProcessor(1)); + Child = new HealthProcessorContainer(healthProcessor) + { + Child = layer = new FailingLayer() + }; + layer.ShowHealth.BindTo(showHealth); }); AddStep("show health", () => showHealth.Value = true); AddStep("enable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true)); - AddUntilStep("layer is visible", () => layer.IsPresent); } [Test] public void TestLayerFading() { + create(new DrainingHealthProcessor(1)); + AddSliderStep("current health", 0.0, 1.0, 1.0, val => { if (layer != null) @@ -53,6 +62,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLayerDisabledViaConfig() { + create(new DrainingHealthProcessor(1)); + AddUntilStep("layer is visible", () => layer.IsPresent); AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddUntilStep("layer is not visible", () => !layer.IsPresent); @@ -61,7 +72,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLayerVisibilityWithAccumulatingProcessor() { - AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new AccumulatingHealthProcessor(1))); + create(new AccumulatingHealthProcessor(1)); + AddUntilStep("layer is not visible", () => !layer.IsPresent); AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddUntilStep("layer is not visible", () => !layer.IsPresent); } @@ -69,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLayerVisibilityWithDrainingProcessor() { - AddStep("bind accumulating processor", () => layer.BindHealthProcessor(new DrainingHealthProcessor(1))); + create(new DrainingHealthProcessor(1)); AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddWaitStep("wait for potential fade", 10); AddAssert("layer is still visible", () => layer.IsPresent); @@ -78,6 +90,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLayerVisibilityWithDifferentOptions() { + create(new DrainingHealthProcessor(1)); + AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddStep("don't show health", () => showHealth.Value = false); @@ -96,5 +110,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("enable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer fade is visible", () => layer.IsPresent); } + + private class HealthProcessorContainer : Container + { + [Cached(typeof(HealthProcessor))] + private readonly HealthProcessor healthProcessor; + + public HealthProcessorContainer(HealthProcessor healthProcessor) + { + this.healthProcessor = healthProcessor; + } + } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 55c681b605..a451df026b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -23,6 +23,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private ScoreProcessor scoreProcessor = new ScoreProcessor(); + [Cached] + private HealthProcessor healthProcessor = new DrainingHealthProcessor(1); + // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); @@ -143,7 +146,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create overlay", () => { - hudOverlay = new HUDOverlay(scoreProcessor, null, null, Array.Empty()); + hudOverlay = new HUDOverlay(scoreProcessor, null, Array.Empty()); // Add any key just to display the key counter visually. hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 086bcb19c3..270216564f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap); - var hudOverlay = new HUDOverlay(scoreProcessor, null, drawableRuleset, Array.Empty()) + var hudOverlay = new HUDOverlay(scoreProcessor, drawableRuleset, Array.Empty()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual.Gameplay // 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 { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 8131c77b4b..12ce20f58e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay { SetContents(() => { - hudOverlay = new HUDOverlay(scoreProcessor, null, null, Array.Empty()); + hudOverlay = new HUDOverlay(scoreProcessor, null, Array.Empty()); // Add any key just to display the key counter visually. hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index 5bac8582d7..f06236d0a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -4,11 +4,13 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay @@ -19,6 +21,9 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + [Cached] + private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [SetUpSteps] public void SetUpSteps() { @@ -28,8 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddStep(@"Reset all", delegate { - foreach (var s in healthDisplays) - s.Current.Value = 1; + healthProcessor.Health.Value = 1; }); } @@ -38,23 +42,18 @@ namespace osu.Game.Tests.Visual.Gameplay { AddRepeatStep(@"decrease hp", delegate { - foreach (var healthDisplay in healthDisplays) - healthDisplay.Current.Value -= 0.08f; + healthProcessor.Health.Value = 0.08f; }, 10); AddRepeatStep(@"increase hp without flash", delegate { - foreach (var healthDisplay in healthDisplays) - healthDisplay.Current.Value += 0.1f; + healthProcessor.Health.Value = 0.1f; }, 3); AddRepeatStep(@"increase hp with flash", delegate { - foreach (var healthDisplay in healthDisplays) - { - healthDisplay.Current.Value += 0.1f; - healthDisplay.Flash(new JudgementResult(null, new OsuJudgement())); - } + healthProcessor.Health.Value = 0.1f; + healthProcessor.ApplyResult(new JudgementResult(null, new OsuJudgement())); }, 3); } } diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index e3cd71691d..27f81467cb 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Play.HUD GlowColour = colours.BlueDarker; } - public override void Flash(JudgementResult result) => Scheduler.AddOnce(flash); + protected override void Flash(JudgementResult result) => Scheduler.AddOnce(flash); private void flash() { diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 847b8a53cf..e071337f34 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -39,7 +39,6 @@ namespace osu.Game.Screens.Play.HUD private readonly Container boxes; private Bindable fadePlayfieldWhenHealthLow; - private HealthProcessor healthProcessor; public FailingLayer() { @@ -88,18 +87,10 @@ namespace osu.Game.Screens.Play.HUD updateState(); } - public override void BindHealthProcessor(HealthProcessor processor) - { - base.BindHealthProcessor(processor); - - healthProcessor = processor; - updateState(); - } - private void updateState() { // Don't display ever if the ruleset is not using a draining health display. - var showLayer = healthProcessor is DrainingHealthProcessor && fadePlayfieldWhenHealthLow.Value && ShowHealth.Value; + var showLayer = HealthProcessor is DrainingHealthProcessor && fadePlayfieldWhenHealthLow.Value && ShowHealth.Value; this.FadeTo(showLayer ? 1 : 0, fade_time, Easing.OutQuint); } diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 5c43e00192..2292f64989 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; @@ -11,26 +12,42 @@ namespace osu.Game.Screens.Play.HUD { /// /// A container for components displaying the current player health. - /// Gets bound automatically to the when inserted to hierarchy. + /// Gets bound automatically to the when inserted to hierarchy. /// - public abstract class HealthDisplay : Container, IHealthDisplay + public abstract class HealthDisplay : Container { + [Resolved] + protected HealthProcessor HealthProcessor { get; private set; } + public Bindable Current { get; } = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; - public virtual void Flash(JudgementResult result) + protected virtual void Flash(JudgementResult result) { } - /// - /// Bind the tracked fields of to this health display. - /// - public virtual void BindHealthProcessor(HealthProcessor processor) + [BackgroundDependencyLoader] + private void load() { - Current.BindTo(processor.Health); + Current.BindTo(HealthProcessor.Health); + + HealthProcessor.NewJudgement += onNewJudgement; + } + + private void onNewJudgement(JudgementResult judgement) + { + if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit) Flash(judgement); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (HealthProcessor != null) + HealthProcessor.NewJudgement -= onNewJudgement; } } } diff --git a/osu.Game/Screens/Play/HUD/IHealthDisplay.cs b/osu.Game/Screens/Play/HUD/IHealthDisplay.cs deleted file mode 100644 index b1a64bd844..0000000000 --- a/osu.Game/Screens/Play/HUD/IHealthDisplay.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Judgements; - -namespace osu.Game.Screens.Play.HUD -{ - /// - /// An interface providing a set of methods to update a health display. - /// - public interface IHealthDisplay : IDrawable - { - /// - /// The current health to be displayed. - /// - Bindable Current { get; } - - /// - /// Flash the display for a specified result type. - /// - /// The result type. - void Flash(JudgementResult result); - } -} diff --git a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs index 1f91f5e50f..ef0affa417 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs @@ -3,13 +3,12 @@ using System; using osu.Framework.Bindables; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay + public class SkinnableHealthDisplay : SkinnableDrawable { public Bindable Current { get; } = new BindableDouble(1) { @@ -17,8 +16,6 @@ namespace osu.Game.Screens.Play.HUD MaxValue = 1 }; - public void Flash(JudgementResult result) => skinnedCounter?.Flash(result); - private HealthProcessor processor; public void BindHealthProcessor(HealthProcessor processor) @@ -36,15 +33,5 @@ namespace osu.Game.Screens.Play.HUD { CentreComponent = false; } - - private IHealthDisplay skinnedCounter; - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - skinnedCounter = Drawable as IHealthDisplay; - skinnedCounter?.Current.BindTo(Current); - } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 83897c5167..31a6bc7678 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -47,7 +47,6 @@ namespace osu.Game.Screens.Play public Bindable ShowHealthbar = new Bindable(true); private readonly ScoreProcessor scoreProcessor; - private readonly HealthProcessor healthProcessor; private readonly DrawableRuleset drawableRuleset; private readonly IReadOnlyList mods; @@ -75,10 +74,9 @@ namespace osu.Game.Screens.Play private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter, topRightElements }; - public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { this.scoreProcessor = scoreProcessor; - this.healthProcessor = healthProcessor; this.drawableRuleset = drawableRuleset; this.mods = mods; @@ -161,9 +159,6 @@ namespace osu.Game.Screens.Play if (scoreProcessor != null) BindScoreProcessor(scoreProcessor); - if (healthProcessor != null) - BindHealthProcessor(healthProcessor); - if (drawableRuleset != null) { BindDrawableRuleset(drawableRuleset); @@ -322,21 +317,6 @@ namespace osu.Game.Screens.Play { ScoreCounter?.Current.BindTo(processor.TotalScore); AccuracyCounter?.Current.BindTo(processor.Accuracy); - - if (HealthDisplay is IHealthDisplay shd) - { - processor.NewJudgement += judgement => - { - if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit) - shd.Flash(judgement); - }; - } - } - - protected virtual void BindHealthProcessor(HealthProcessor processor) - { - HealthDisplay?.BindHealthProcessor(processor); - FailingLayer?.BindHealthProcessor(processor); } public bool OnPressed(GlobalAction action) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 951ce334f6..1538899281 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -208,6 +208,8 @@ namespace osu.Game.Screens.Play HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); + dependencies.CacheAs(HealthProcessor); + if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); @@ -343,7 +345,7 @@ namespace osu.Game.Screens.Play // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(ScoreProcessor, HealthProcessor, DrawableRuleset, Mods.Value) + HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value) { HoldToQuit = { diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 2e29abf453..c1979efbc2 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, ISkinnableComponent + public class LegacyHealthDisplay : HealthDisplay { private const double epic_cutoff = 0.5; @@ -28,12 +28,6 @@ namespace osu.Game.Skinning private bool isNewStyle; - public Bindable Current { get; } = new BindableDouble(1) - { - MinValue = 0, - MaxValue = 1 - }; - public LegacyHealthDisplay(Skin skin) { this.skin = skin; @@ -83,7 +77,7 @@ namespace osu.Game.Skinning marker.Position = fill.Position + new Vector2(fill.DrawWidth, isNewStyle ? fill.DrawHeight / 2 : 0); } - public void Flash(JudgementResult result) => marker.Flash(result); + protected override void Flash(JudgementResult result) => marker.Flash(result); private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}"); @@ -254,7 +248,7 @@ namespace osu.Game.Skinning Main.ScaleTo(1.4f).Then().ScaleTo(1, 200, Easing.Out); } - public class LegacyHealthPiece : CompositeDrawable, IHealthDisplay + public class LegacyHealthPiece : CompositeDrawable { public Bindable Current { get; } = new Bindable(); From 84a4ff333eec332a3c5dbd9b167f15b17fc6db7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 17:10:31 +0900 Subject: [PATCH 409/708] Update skin editor test scene to cache a `ScoreProcessor` --- .../Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 270216564f..d50d4aa920 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -17,6 +18,9 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinEditorMultipleSkins : SkinnableTestScene { + [Cached] + private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(); + [SetUpSteps] public void SetUpSteps() { @@ -28,8 +32,6 @@ namespace osu.Game.Tests.Visual.Gameplay 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, drawableRuleset, Array.Empty()) @@ -40,6 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay // Add any key just to display the key counter visually. hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + scoreProcessor.Combo.Value = 1; return new Container { From 8e78cac05826b2e01959e3971a8043d0f53cf9df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 17:31:29 +0900 Subject: [PATCH 410/708] Fix `HealthProcessor` cached as derived type in test --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index a451df026b..accb2ba1d9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private ScoreProcessor scoreProcessor = new ScoreProcessor(); - [Cached] + [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(1); // best way to check without exposing. From 6c255a05726faed8e6244ded666cee31966a5f72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 17:47:33 +0900 Subject: [PATCH 411/708] Fix drain start time being weirdly incorrect --- osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs | 8 ++++---- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index e426726def..99facb2731 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLayerFading() { - create(new DrainingHealthProcessor(1)); + create(new DrainingHealthProcessor(0)); AddSliderStep("current health", 0.0, 1.0, 1.0, val => { @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLayerDisabledViaConfig() { - create(new DrainingHealthProcessor(1)); + create(new DrainingHealthProcessor(0)); AddUntilStep("layer is visible", () => layer.IsPresent); AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddStep("set health to 0.10", () => layer.Current.Value = 0.1); @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLayerVisibilityWithDrainingProcessor() { - create(new DrainingHealthProcessor(1)); + create(new DrainingHealthProcessor(0)); AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddWaitStep("wait for potential fade", 10); AddAssert("layer is still visible", () => layer.IsPresent); @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLayerVisibilityWithDifferentOptions() { - create(new DrainingHealthProcessor(1)); + create(new DrainingHealthProcessor(0)); AddStep("set health to 0.10", () => layer.Current.Value = 0.1); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index accb2ba1d9..861d4a4f7f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay private ScoreProcessor scoreProcessor = new ScoreProcessor(); [Cached(typeof(HealthProcessor))] - private HealthProcessor healthProcessor = new DrainingHealthProcessor(1); + private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; From 3044b1c432621823fb9bd25b5190b38988ff8696 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 17:47:38 +0900 Subject: [PATCH 412/708] Add missing cache rules --- .../Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs | 3 +++ .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 3 +++ .../Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs | 5 +++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index d50d4aa920..ac5599cc27 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -21,6 +21,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(); + [Cached(typeof(HealthProcessor))] + private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + [SetUpSteps] public void SetUpSteps() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 12ce20f58e..7a960a09fc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -27,6 +27,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private ScoreProcessor scoreProcessor = new ScoreProcessor(); + [Cached(typeof(HealthProcessor))] + private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + private IEnumerable hudOverlays => CreatedDrawables.OfType(); // best way to check without exposing. diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index f06236d0a8..2d6a6d95c7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -21,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); - [Cached] + [Cached(typeof(HealthProcessor))] private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); [SetUpSteps] @@ -53,7 +54,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep(@"increase hp with flash", delegate { healthProcessor.Health.Value = 0.1f; - healthProcessor.ApplyResult(new JudgementResult(null, new OsuJudgement())); + healthProcessor.ApplyResult(new JudgementResult(new HitCircle(), new OsuJudgement())); }, 3); } } From 1cb10c2a2243bde4f0fcc85a5d6de338a34f28bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 17:27:34 +0900 Subject: [PATCH 413/708] Remove unnecessary binding logic from `HUDOverlay` --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- .../TestSceneSkinEditorMultipleSkins.cs | 2 +- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 2 +- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 6 +++--- osu.Game/Screens/Play/HUDOverlay.cs | 21 ++++--------------- osu.Game/Screens/Play/Player.cs | 2 +- 6 files changed, 11 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 861d4a4f7f..b7e92a79a0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create overlay", () => { - hudOverlay = new HUDOverlay(scoreProcessor, null, Array.Empty()); + hudOverlay = new HUDOverlay(null, Array.Empty()); // Add any key just to display the key counter visually. hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index ac5599cc27..c7c93b8892 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap); - var hudOverlay = new HUDOverlay(scoreProcessor, drawableRuleset, Array.Empty()) + var hudOverlay = new HUDOverlay(drawableRuleset, Array.Empty()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 7a960a09fc..c92e9dcfd5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Gameplay { SetContents(() => { - hudOverlay = new HUDOverlay(scoreProcessor, null, Array.Empty()); + hudOverlay = new HUDOverlay(null, Array.Empty()); // Add any key just to display the key counter visually. hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 37d10a5320..998a9fb7e7 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -22,11 +22,11 @@ namespace osu.Game.Screens.Play.HUD private readonly HitWindows hitWindows; - private readonly ScoreProcessor processor; + [Resolved] + private ScoreProcessor processor { get; set; } - public HitErrorDisplay(ScoreProcessor processor, HitWindows hitWindows) + public HitErrorDisplay(HitWindows hitWindows) { - this.processor = processor; this.hitWindows = hitWindows; RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index fd1aea016b..22812b779f 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -14,7 +14,6 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; using osuTK; @@ -39,14 +38,11 @@ namespace osu.Game.Screens.Play public readonly SkinnableHealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; - public readonly HitErrorDisplay HitErrorDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - public readonly FailingLayer FailingLayer; public Bindable ShowHealthbar = new Bindable(true); - private readonly ScoreProcessor scoreProcessor; private readonly DrawableRuleset drawableRuleset; private readonly IReadOnlyList mods; @@ -74,9 +70,8 @@ namespace osu.Game.Screens.Play private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter, topRightElements }; - public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) + public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods) { - this.scoreProcessor = scoreProcessor; this.drawableRuleset = drawableRuleset; this.mods = mods; @@ -84,7 +79,7 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { - FailingLayer = CreateFailingLayer(), + CreateFailingLayer(), visibilityContainer = new Container { RelativeSizeAxes = Axes.Both, @@ -104,7 +99,7 @@ namespace osu.Game.Screens.Play AccuracyCounter = CreateAccuracyCounter(), ScoreCounter = CreateScoreCounter(), CreateComboCounter(), - HitErrorDisplay = CreateHitErrorDisplayOverlay(), + CreateHitErrorDisplayOverlay(), } }, }, @@ -156,9 +151,6 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader(true)] private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) { - if (scoreProcessor != null) - BindScoreProcessor(scoreProcessor); - if (drawableRuleset != null) { BindDrawableRuleset(drawableRuleset); @@ -309,15 +301,10 @@ namespace osu.Game.Screens.Play AutoSizeAxes = Axes.Both, }; - protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows); + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(drawableRuleset?.FirstAvailableHitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); - protected virtual void BindScoreProcessor(ScoreProcessor processor) - { - AccuracyCounter?.Current.BindTo(processor.Accuracy); - } - public bool OnPressed(GlobalAction action) { switch (action) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1538899281..116cf3cc99 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -345,7 +345,7 @@ namespace osu.Game.Screens.Play // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value) + HUDOverlay = new HUDOverlay(DrawableRuleset, Mods.Value) { HoldToQuit = { From 111b501ced835f83a4001fc3948eb26816c08364 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 7 May 2021 18:04:38 +0900 Subject: [PATCH 414/708] Revert accidental removal of UTF-8 BOM --- osu.Game/Rulesets/Replays/AutoGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index 6ce857b14b..83e85146d4 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; From 9fe6e1096ab9d9264f97f52ba8ed7ae504983d0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 18:11:08 +0900 Subject: [PATCH 415/708] Remove cruft from `SkinnableHealthDisplay` --- .../Play/HUD/SkinnableHealthDisplay.cs | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs index ef0affa417..3ba6a33276 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs @@ -1,33 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Bindables; -using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { public class SkinnableHealthDisplay : SkinnableDrawable { - public Bindable Current { get; } = new BindableDouble(1) - { - MinValue = 0, - MaxValue = 1 - }; - - private HealthProcessor processor; - - public void BindHealthProcessor(HealthProcessor processor) - { - if (this.processor != null) - throw new InvalidOperationException("Can't bind to a processor more than once"); - - this.processor = processor; - - Current.BindTo(processor.Health); - } - public SkinnableHealthDisplay() : base(new HUDSkinComponent(HUDSkinComponents.HealthDisplay), _ => new DefaultHealthDisplay()) { From a1aeac567710b95f27b07daede58b9a1ad56ab45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 18:11:59 +0900 Subject: [PATCH 416/708] Remove remaining cruft from `SkinnableAccuracyCounter` --- osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs index 17c2493d2d..fcb8fca35d 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs @@ -1,15 +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.Bindables; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { public class SkinnableAccuracyCounter : SkinnableDrawable { - public Bindable Current { get; } = new Bindable(); - public SkinnableAccuracyCounter() : base(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter), _ => new DefaultAccuracyCounter()) { From 5b2f786f971ef65100be176ebc828b87e87f8c3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 19:16:00 +0900 Subject: [PATCH 417/708] 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 99cda7693d..80b1c5b52f 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 e448972066..29189781a7 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 43ed2d7dc8..c4eb7aefba 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 8c564a69ed1978e2e11be2d93806c57a759c5800 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Fri, 7 May 2021 20:59:20 -0400 Subject: [PATCH 418/708] Fix InvalidOperationException when exiting a map at the end --- .../Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 10 ++++++++++ osu.Game/Screens/Play/Player.cs | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 4138a81ebd..e1dc6e3b42 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -133,6 +133,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); } + [Test] + public void TestPerformExitNoOutro() + { + CreateTest(null); + AddStep("disable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, false)); + AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); + AddStep("exit via pause", () => Player.ExitViaPause()); + AddAssert("score shown", () => Player.IsScoreShown); + } + protected override bool AllowFail => true; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 88e617245b..0a2f9f0d18 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -542,7 +542,11 @@ 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) + { + completionProgressDelegate?.Cancel(); + completionProgressDelegate = null; updateCompletionState(true); + } } this.Exit(); From 0f08c2a4799d3002861b199205c0e20e4498ff23 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 11:44:29 +0300 Subject: [PATCH 419/708] Add star rating display underneath the beatmap metadata --- .../Screens/Play/BeatmapMetadataDisplay.cs | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index c56344a8fb..7ad634afed 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -12,8 +12,10 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Ranking.Expanded; using osuTK; namespace osu.Game.Screens.Play @@ -30,6 +32,9 @@ namespace osu.Game.Screens.Play public IBindable> Mods => mods; + [Resolved] + private IBindable ruleset { get; set; } + public bool Loading { set @@ -51,10 +56,12 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load() + private void load(BeatmapDifficultyCache difficultyCache) { var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); + var starDifficulty = difficultyCache.GetDifficultyAsync(beatmap.BeatmapInfo, ruleset.Value, mods.Value).Result; + AutoSizeAxes = Axes.Both; Children = new Drawable[] { @@ -107,16 +114,29 @@ namespace osu.Game.Screens.Play loading = new LoadingLayer(true) } }, - new OsuSpriteText + new FillFlowContainer { - Text = beatmap?.BeatmapInfo?.Version, - Font = OsuFont.GetFont(size: 26, italics: true), - Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, Anchor = Anchor.TopCentre, - Margin = new MarginPadding + Origin = Anchor.TopCentre, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5f), + Margin = new MarginPadding { Bottom = 40 }, + Children = new Drawable[] { - Bottom = 40 - }, + new OsuSpriteText + { + Text = beatmap?.BeatmapInfo?.Version, + Font = OsuFont.GetFont(size: 26, italics: true), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new StarRatingDisplay(starDifficulty) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + } }, new GridContainer { From 7b7e7a86bf70423464fa078b10b52301790ad5dc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 11:46:28 +0300 Subject: [PATCH 420/708] Allow null logo facade --- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 7ad634afed..8e760c38ba 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -27,7 +28,7 @@ namespace osu.Game.Screens.Play { private readonly WorkingBeatmap beatmap; private readonly Bindable> mods; - private readonly Drawable facade; + private readonly Drawable logoFacade; private LoadingSpinner loading; public IBindable> Mods => mods; @@ -46,10 +47,10 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable> mods, Drawable facade) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable> mods, [CanBeNull] Drawable logoFacade) { this.beatmap = beatmap; - this.facade = facade; + this.logoFacade = logoFacade; this.mods = new Bindable>(); this.mods.BindTo(mods); @@ -73,11 +74,11 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Vertical, Children = new[] { - facade.With(d => + logoFacade?.With(d => { d.Anchor = Anchor.TopCentre; d.Origin = Anchor.TopCentre; - }), + }) ?? Drawable.Empty(), new OsuSpriteText { Text = new RomanisableString(metadata.TitleUnicode, metadata.Title), From 169a28340286aeca24e73f47ce76c190c3f7c87d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 11:46:42 +0300 Subject: [PATCH 421/708] Add visual test scene --- .../TestSceneBeatmapMetadataDisplay.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs new file mode 100644 index 0000000000..02c9c6b7a3 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelect +{ + [System.ComponentModel.Description("player loader beatmap metadata")] + public class TestSceneBeatmapMetadataDisplay : OsuTestScene + { + private BeatmapMetadataDisplay display; + + [Resolved] + private BeatmapManager manager { get; set; } + + private IReadOnlyList randomMods => Ruleset.Value.CreateInstance() + .GetAllMods() + .OrderBy(_ => RNG.Next()) + .Take(5) + .ToList(); + + private void createDisplay(Func getBeatmap) + { + AddStep("setup display", () => Child = display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(randomMods), null) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.5f), + }); + + AddToggleStep("trigger loading", v => display.Loading = v); + } + + [Test] + public void TestLocal([Values("Beatmap", "Some long title and stuff")] + string title, + [Values("Trial", "Some1's very hardest difficulty")] + string version) + { + createDisplay(() => CreateWorkingBeatmap(new Beatmap + { + BeatmapInfo = + { + Metadata = new BeatmapMetadata + { + Title = title, + }, + Version = version, + StarDifficulty = RNG.NextDouble(0, 10), + } + })); + } + + [Test] + public void TestRandomFromDatabase() + { + createDisplay(() => + { + var allBeatmapSets = manager.GetAllUsableBeatmapSets(IncludedDetails.Minimal); + var randomBeatmapSet = allBeatmapSets[RNG.Next(0, allBeatmapSets.Count - 1)]; + var randomBeatmap = randomBeatmapSet.Beatmaps[RNG.Next(0, randomBeatmapSet.Beatmaps.Count - 1)]; + + return manager.GetWorkingBeatmap(randomBeatmap); + }); + } + } +} From b4801faf32bed6a2bc279305d4237930f60a683d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 11:57:12 +0300 Subject: [PATCH 422/708] Pass ruleset info to constructor instead Follows the way working beatmap is passed, not sure why mods are passed as a bindable though, don't wanna bother too much with that. --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 2 +- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 9 ++++----- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 02c9c6b7a3..ee87877860 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void createDisplay(Func getBeatmap) { - AddStep("setup display", () => Child = display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(randomMods), null) + AddStep("setup display", () => Child = display = new BeatmapMetadataDisplay(getBeatmap(), Ruleset.Value, new Bindable>(randomMods), null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 8e760c38ba..daaf3b73cb 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -27,15 +27,13 @@ namespace osu.Game.Screens.Play public class BeatmapMetadataDisplay : Container { private readonly WorkingBeatmap beatmap; + private readonly RulesetInfo ruleset; private readonly Bindable> mods; private readonly Drawable logoFacade; private LoadingSpinner loading; public IBindable> Mods => mods; - [Resolved] - private IBindable ruleset { get; set; } - public bool Loading { set @@ -47,9 +45,10 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable> mods, [CanBeNull] Drawable logoFacade) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, RulesetInfo ruleset, Bindable> mods, [CanBeNull] Drawable logoFacade) { this.beatmap = beatmap; + this.ruleset = ruleset; this.logoFacade = logoFacade; this.mods = new Bindable>(); @@ -61,7 +60,7 @@ namespace osu.Game.Screens.Play { var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); - var starDifficulty = difficultyCache.GetDifficultyAsync(beatmap.BeatmapInfo, ruleset.Value, mods.Value).Result; + var starDifficulty = difficultyCache.GetDifficultyAsync(beatmap.BeatmapInfo, ruleset, mods.Value).Result; AutoSizeAxes = Axes.Both; Children = new Drawable[] diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index ce580e2b53..066ca25790 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -134,7 +134,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, }).WithChildren(new Drawable[] { - MetadataInfo = new BeatmapMetadataDisplay(Beatmap.Value, Mods, content.LogoFacade) + MetadataInfo = new BeatmapMetadataDisplay(Beatmap.Value, Ruleset.Value, Mods, content.LogoFacade) { Alpha = 0, Anchor = Anchor.Centre, From 0410edecaff273d81d023af07044bbd46a88b9a5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 12:55:07 +0300 Subject: [PATCH 423/708] Refactor `StarRatingDisplay` to be mutable with a current bindable --- .../Ranking/TestSceneStarRatingDisplay.cs | 15 +++- .../Ranking/Expanded/StarRatingDisplay.cs | 87 ++++++++++++------- 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs index d0067c3396..a043f506c1 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.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 NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Screens.Ranking.Expanded; @@ -10,8 +12,11 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneStarRatingDisplay : OsuTestScene { - public TestSceneStarRatingDisplay() + [SetUp] + public void SetUp() => Schedule(() => { + StarRatingDisplay changingStarRating; + Child = new FillFlowContainer { Anchor = Anchor.Centre, @@ -25,8 +30,14 @@ namespace osu.Game.Tests.Visual.Ranking new StarRatingDisplay(new StarDifficulty(5.67, 0)), new StarRatingDisplay(new StarDifficulty(6.78, 0)), new StarRatingDisplay(new StarDifficulty(10.11, 0)), + changingStarRating = new StarRatingDisplay(), } }; - } + + Scheduler.AddDelayed(() => + { + changingStarRating.Current.Value = new StarDifficulty(RNG.NextDouble(0, 10), RNG.Next()); + }, 500, true); + }); } } diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index f7e50fdc8a..a1f48fa811 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -3,12 +3,14 @@ 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; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -20,17 +22,29 @@ namespace osu.Game.Screens.Ranking.Expanded /// /// A pill that displays the star rating of a . /// - public class StarRatingDisplay : CompositeDrawable + public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue { - private readonly StarDifficulty difficulty; + private Box background; + private OsuTextFlowContainer textFlow; + + [Resolved] + private OsuColour colours { get; set; } + + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } /// /// Creates a new using an already computed . /// /// The already computed to display the star difficulty of. - public StarRatingDisplay(StarDifficulty starDifficulty) + public StarRatingDisplay(StarDifficulty starDifficulty = default) { - difficulty = starDifficulty; + Current.Value = starDifficulty; } [BackgroundDependencyLoader] @@ -38,15 +52,6 @@ namespace osu.Game.Screens.Ranking.Expanded { AutoSizeAxes = Axes.Both; - var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); - string wholePart = starRatingParts[0]; - string fractionPart = starRatingParts[1]; - string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - - ColourInfo backgroundColour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus - ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); - InternalChildren = new Drawable[] { new CircularContainer @@ -55,10 +60,9 @@ namespace osu.Game.Screens.Ranking.Expanded Masking = true, Children = new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, - Colour = backgroundColour }, } }, @@ -78,32 +82,51 @@ namespace osu.Game.Screens.Ranking.Expanded Icon = FontAwesome.Solid.Star, Colour = Color4.Black }, - new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) + textFlow = 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; - }); - }) + } } } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(difficulty => updateDisplay(difficulty.NewValue), true); + } + + private void updateDisplay(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; + + background.Colour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus + ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) + : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); + + textFlow.Clear(); + + textFlow.AddText($"{wholePart}", s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 14); + s.UseFullGlyphHeight = false; + }); + + textFlow.AddText($"{separator}{fractionPart}", s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 7); + s.UseFullGlyphHeight = false; + }); + } } } From 43090067da8a199eca6e2d4202d0bef1eae7fe5c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 12:59:59 +0300 Subject: [PATCH 424/708] Use `BeatmapDifficultyCache.GetBindableDifficulty(...)` instead --- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index daaf3b73cb..54c739bd9f 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -55,12 +55,14 @@ namespace osu.Game.Screens.Play this.mods.BindTo(mods); } + private IBindable starDifficulty; + [BackgroundDependencyLoader] private void load(BeatmapDifficultyCache difficultyCache) { - var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); + StarRatingDisplay starRatingDisplay; - var starDifficulty = difficultyCache.GetDifficultyAsync(beatmap.BeatmapInfo, ruleset, mods.Value).Result; + var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); AutoSizeAxes = Axes.Both; Children = new Drawable[] @@ -131,7 +133,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - new StarRatingDisplay(starDifficulty) + starRatingDisplay = new StarRatingDisplay { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -179,6 +181,13 @@ namespace osu.Game.Screens.Play } }; + starDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo); + starDifficulty.BindValueChanged(difficulty => + { + if (difficulty.NewValue is StarDifficulty diff) + starRatingDisplay.Current.Value = diff; + }, true); + Loading = true; } From dca5efc59afc5876b794e6cb3610e4cb18f3a64d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 13:00:39 +0300 Subject: [PATCH 425/708] Remove no longer necessary ruleset info requirement --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 2 +- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 5 +---- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index ee87877860..02c9c6b7a3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void createDisplay(Func getBeatmap) { - AddStep("setup display", () => Child = display = new BeatmapMetadataDisplay(getBeatmap(), Ruleset.Value, new Bindable>(randomMods), null) + AddStep("setup display", () => Child = display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(randomMods), null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 54c739bd9f..670d99c462 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -13,7 +13,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking.Expanded; @@ -27,7 +26,6 @@ namespace osu.Game.Screens.Play public class BeatmapMetadataDisplay : Container { private readonly WorkingBeatmap beatmap; - private readonly RulesetInfo ruleset; private readonly Bindable> mods; private readonly Drawable logoFacade; private LoadingSpinner loading; @@ -45,10 +43,9 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap, RulesetInfo ruleset, Bindable> mods, [CanBeNull] Drawable logoFacade) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable> mods, [CanBeNull] Drawable logoFacade) { this.beatmap = beatmap; - this.ruleset = ruleset; this.logoFacade = logoFacade; this.mods = new Bindable>(); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 066ca25790..ce580e2b53 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -134,7 +134,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, }).WithChildren(new Drawable[] { - MetadataInfo = new BeatmapMetadataDisplay(Beatmap.Value, Ruleset.Value, Mods, content.LogoFacade) + MetadataInfo = new BeatmapMetadataDisplay(Beatmap.Value, Mods, content.LogoFacade) { Alpha = 0, Anchor = Anchor.Centre, From 26c0010fe659915ebebb152be37032b6003b4ff3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 13:03:50 +0300 Subject: [PATCH 426/708] Fix test not handling 0 beatmap sets --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 02c9c6b7a3..5ea7a0e83b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -68,6 +68,9 @@ namespace osu.Game.Tests.Visual.SongSelect createDisplay(() => { var allBeatmapSets = manager.GetAllUsableBeatmapSets(IncludedDetails.Minimal); + if (allBeatmapSets.Count == 0) + return manager.DefaultBeatmap; + var randomBeatmapSet = allBeatmapSets[RNG.Next(0, allBeatmapSets.Count - 1)]; var randomBeatmap = randomBeatmapSet.Beatmaps[RNG.Next(0, randomBeatmapSet.Beatmaps.Count - 1)]; From df630d94282250064ba396d06b518b3ab369447d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 May 2021 14:53:19 +0200 Subject: [PATCH 427/708] Trim redundant `this` qualifier --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 32d2fba25e..d57acd005b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -240,7 +240,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), }; - public override Vector2 ScreenSpaceSelectionPoint => this.ToScreenSpace(slider.HitObject.StackedPosition); + public override Vector2 ScreenSpaceSelectionPoint => ToScreenSpace(slider.HitObject.StackedPosition); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => slider.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; From 63e267a3beefbb0c7344521af93d1a340a5a454c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 May 2021 23:32:52 +0900 Subject: [PATCH 428/708] Revert "Trim redundant `this` qualifier" This reverts commit df630d94282250064ba396d06b518b3ab369447d. --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index d57acd005b..32d2fba25e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -240,7 +240,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), }; - public override Vector2 ScreenSpaceSelectionPoint => ToScreenSpace(slider.HitObject.StackedPosition); + public override Vector2 ScreenSpaceSelectionPoint => this.ToScreenSpace(slider.HitObject.StackedPosition); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => slider.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; From 01d732bb65202f66737d478457d62f1af1cd1884 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 May 2021 23:33:03 +0900 Subject: [PATCH 429/708] Revert "Refactor `SliderSelectionBlueprint` to not reference blueprint pieces for input handling" This reverts commit 54fe10c82a855a16c88f8781a9ae12dc42df7b0e. --- .../Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs | 6 ++++++ .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index ece9c7a757..6e22c35ab3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -26,6 +26,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { AccentColour = Color4.Transparent }; + + // SliderSelectionBlueprint relies on calling ReceivePositionalInputAt on this drawable to determine whether selection should occur. + // Without AlwaysPresent, a movement in a parent container (ie. the editor composer area resizing) could cause incorrect input handling. + AlwaysPresent = true; } [BackgroundDependencyLoader] @@ -50,5 +54,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } public void RecyclePath() => body.RecyclePath(); + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 32d2fba25e..88fcb1e715 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -240,10 +240,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), }; - public override Vector2 ScreenSpaceSelectionPoint => this.ToScreenSpace(slider.HitObject.StackedPosition); + public override Vector2 ScreenSpaceSelectionPoint => BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => - slider.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; + BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); } From fe86ee629ed8b7a7a36eb7048064192cf9584672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 May 2021 16:30:08 +0200 Subject: [PATCH 430/708] Fix temp files from beatmap listing imports not being cleaned up As reported in #12718, it turns out that temporary files from beatmap set downloads performed via the beatmap listing overlay could remain in the user's filesystem even after the download has concluded. The reason for the issue is a failure in component integration. In the case of online downloads, files are first downloaded to a temporary directory (`C:/Temp` or `/tmp`), with a randomly generated filename, which ends in an extension of `.tmp`. On the other side, `ArchiveModelManager`s have a `ShouldDeleteArchive()` method, which determines whether a file should be deleted after importing. At the time of writing, in the case of beatmap imports the file is only automatically cleaned up if the extension of the file is equal to `.osz`, which was not the case for temporary files. As it turns out, `APIDownloadRequest` has a facility for adjusting the file's extension, via the protected `FileExtension` property. Therefore, use it in the case of `DownloadBeatmapSetRequest` to specify `.osz`, which then will make sure that the `ShouldDeleteArchive()` check in `BeatmapManager` picks it up for clean-up. --- osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs index e8871bef05..2898955de7 100644 --- a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs @@ -23,6 +23,8 @@ namespace osu.Game.Online.API.Requests return req; } + protected override string FileExtension => ".osz"; + protected override string Target => $@"beatmapsets/{Model.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}"; } } From d9605e807049ceabb9af5a53568738a1a424c341 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 18:18:23 +0300 Subject: [PATCH 431/708] Remove test scene description --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 5ea7a0e83b..381ccd2dd3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -16,7 +16,6 @@ using osuTK; namespace osu.Game.Tests.Visual.SongSelect { - [System.ComponentModel.Description("player loader beatmap metadata")] public class TestSceneBeatmapMetadataDisplay : OsuTestScene { private BeatmapMetadataDisplay display; From 3575d9847cd120941ad3737ff140c01184edd4e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 18:21:29 +0300 Subject: [PATCH 432/708] Use regular test steps rather than one-time set up and scheduling --- .../Visual/Ranking/TestSceneStarRatingDisplay.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs index a043f506c1..2ff664a0d9 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs @@ -12,12 +12,12 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneStarRatingDisplay : OsuTestScene { - [SetUp] - public void SetUp() => Schedule(() => + [Test] + public void TestDisplay() { - StarRatingDisplay changingStarRating; + StarRatingDisplay changingStarRating = null; - Child = new FillFlowContainer + AddStep("load displays", () => Child = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -32,12 +32,12 @@ namespace osu.Game.Tests.Visual.Ranking new StarRatingDisplay(new StarDifficulty(10.11, 0)), changingStarRating = new StarRatingDisplay(), } - }; + }); - Scheduler.AddDelayed(() => + AddRepeatStep("change bottom rating", () => { changingStarRating.Current.Value = new StarDifficulty(RNG.NextDouble(0, 10), RNG.Next()); - }, 500, true); - }); + }, 10); + } } } From a75347cb2a5dc933b9f2952d3449208379eee935 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 18:43:17 +0300 Subject: [PATCH 433/708] Remove nullable facade logic --- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 670d99c462..98829d079b 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -43,7 +42,7 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable> mods, [CanBeNull] Drawable logoFacade) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable> mods, Drawable logoFacade) { this.beatmap = beatmap; this.logoFacade = logoFacade; @@ -72,11 +71,11 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Vertical, Children = new[] { - logoFacade?.With(d => + logoFacade.With(d => { d.Anchor = Anchor.TopCentre; d.Origin = Anchor.TopCentre; - }) ?? Drawable.Empty(), + }), new OsuSpriteText { Text = new RomanisableString(metadata.TitleUnicode, metadata.Title), From ca55287dd07a9d958e35c738d331dad21b8295ec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 18:43:45 +0300 Subject: [PATCH 434/708] Pass empty facade and replace random property with method instead --- .../SongSelect/TestSceneBeatmapMetadataDisplay.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 381ccd2dd3..552d19ac26 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -23,15 +23,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Resolved] private BeatmapManager manager { get; set; } - private IReadOnlyList randomMods => Ruleset.Value.CreateInstance() - .GetAllMods() - .OrderBy(_ => RNG.Next()) - .Take(5) - .ToList(); - private void createDisplay(Func getBeatmap) { - AddStep("setup display", () => Child = display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(randomMods), null) + AddStep("setup display", () => Child = display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(getRandomMods()), Empty()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -76,5 +70,11 @@ namespace osu.Game.Tests.Visual.SongSelect return manager.GetWorkingBeatmap(randomBeatmap); }); } + + private IReadOnlyList getRandomMods() => Ruleset.Value.CreateInstance() + .GetAllMods() + .OrderBy(_ => RNG.Next()) + .Take(5) + .ToList(); } } From 655e8d3d867c5a2956caeea928e0704c98b580e6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 18:44:28 +0300 Subject: [PATCH 435/708] Remove pattern-matching on nullable with simple `.HasValue`/`.Value` --- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 98829d079b..d31033ef15 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -180,8 +180,8 @@ namespace osu.Game.Screens.Play starDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo); starDifficulty.BindValueChanged(difficulty => { - if (difficulty.NewValue is StarDifficulty diff) - starRatingDisplay.Current.Value = diff; + if (difficulty.NewValue.HasValue) + starRatingDisplay.Current.Value = difficulty.NewValue.Value; }, true); Loading = true; From 25312b3e88d47b2cbc9ce327d2c6d05954b39d72 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 8 May 2021 11:45:31 -0400 Subject: [PATCH 436/708] Don't restart completion delegate on exit, revert exit behavior to lazer --- .../Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 2 +- osu.Game/Screens/Play/Player.cs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index e1dc6e3b42..70b1d3ef85 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("disable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, false)); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("exit via pause", () => Player.ExitViaPause()); - AddAssert("score shown", () => Player.IsScoreShown); + AddAssert("score not shown", () => !Player.IsScoreShown); } protected override bool AllowFail => true; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0a2f9f0d18..557dd2a78a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -541,10 +541,8 @@ 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) + if (prepareScoreForDisplayTask != null && completionProgressDelegate == null) { - completionProgressDelegate?.Cancel(); - completionProgressDelegate = null; updateCompletionState(true); } } From c52f1733be73b49af4f3dea4257fd0b9eaa5610a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 21:13:18 +0300 Subject: [PATCH 437/708] Apply further refactoring to star rating display UX-wise --- .../Screens/Play/BeatmapMetadataDisplay.cs | 6 +- .../Ranking/Expanded/StarRatingDisplay.cs | 106 +++++++++++++----- 2 files changed, 77 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index d31033ef15..0164fe9179 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -178,11 +178,7 @@ namespace osu.Game.Screens.Play }; starDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo); - starDifficulty.BindValueChanged(difficulty => - { - if (difficulty.NewValue.HasValue) - starRatingDisplay.Current.Value = difficulty.NewValue.Value; - }, true); + starDifficulty.BindValueChanged(d => starRatingDisplay.Current.Value = d.NewValue, true); Loading = true; } diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index a1f48fa811..77c43ad863 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -13,7 +13,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -22,27 +22,50 @@ namespace osu.Game.Screens.Ranking.Expanded /// /// A pill that displays the star rating of a . /// - public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue + public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue { private Box background; - private OsuTextFlowContainer textFlow; + private OsuSpriteText wholePart; + private OsuSpriteText fractionPart; + + private double displayedStarRating; + + protected double DisplayedStarRating + { + get => displayedStarRating; + set + { + displayedStarRating = value; + + var starRatingParts = value.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); + wholePart.Text = starRatingParts[0]; + fractionPart.Text = starRatingParts[1]; + } + } [Resolved] private OsuColour colours { get; set; } - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); - public Bindable Current + public Bindable Current { get => current.Current; set => current.Current = value; } + /// + /// Creates a new without any set, displaying a placeholder until is changed. + /// + public StarRatingDisplay() + { + } + /// /// Creates a new using an already computed . /// /// The already computed to display the star difficulty of. - public StarRatingDisplay(StarDifficulty starDifficulty = default) + public StarRatingDisplay(StarDifficulty starDifficulty) { Current.Value = starDifficulty; } @@ -82,13 +105,40 @@ namespace osu.Game.Screens.Ranking.Expanded Icon = FontAwesome.Solid.Star, Colour = Color4.Black }, - textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) + new FillFlowContainer { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - TextAnchor = Anchor.BottomLeft, + Children = new[] + { + wholePart = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Colour = Color4.Black, + Font = OsuFont.Numeric.With(size: 14, weight: FontWeight.Black), + UseFullGlyphHeight = false, + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Colour = Color4.Black, + Text = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator, + Font = OsuFont.Numeric.With(size: 7, weight: FontWeight.Black), + UseFullGlyphHeight = false, + }, + fractionPart = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Colour = Color4.Black, + Font = OsuFont.Numeric.With(size: 7, weight: FontWeight.Black), + UseFullGlyphHeight = false, + } + } } } } @@ -98,35 +148,31 @@ namespace osu.Game.Screens.Ranking.Expanded protected override void LoadComplete() { base.LoadComplete(); - Current.BindValueChanged(difficulty => updateDisplay(difficulty.NewValue), true); + + Current.BindValueChanged(_ => updateDisplay(), true); + FinishTransforms(true); } - private void updateDisplay(StarDifficulty difficulty) + private void updateDisplay() { - var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); - string wholePart = starRatingParts[0]; - string fractionPart = starRatingParts[1]; - string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + const double duration = 400; + const Easing easing = Easing.OutQuint; - background.Colour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus - ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); + ColourInfo backgroundColour; - textFlow.Clear(); - - textFlow.AddText($"{wholePart}", s => + if (Current.Value == null) + backgroundColour = Color4.SlateGray.Opacity(0.3f); + else { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 14); - s.UseFullGlyphHeight = false; - }); + var rating = Current.Value.Value.DifficultyRating; - textFlow.AddText($"{separator}{fractionPart}", s => - { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 7); - s.UseFullGlyphHeight = false; - }); + backgroundColour = rating == DifficultyRating.ExpertPlus + ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) + : (ColourInfo)colours.ForDifficultyRating(rating); + } + + background.FadeColour(backgroundColour, duration, easing); + this.TransformTo(nameof(DisplayedStarRating), Current.Value?.Stars ?? 0.0f, duration, easing); } } } From f701c331f285834f4814b4e070384a54637214f4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 21:15:15 +0300 Subject: [PATCH 438/708] Add initial fade in to the metadata display Avoids first frame discrepancies from appearing in the test scene, those can be delt with later on, if needed. --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 552d19ac26..f41180acf2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -30,8 +30,10 @@ namespace osu.Game.Tests.Visual.SongSelect Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(1.5f), + Alpha = 0f, }); + AddStep("fade in", () => display.FadeIn(400, Easing.OutQuint)); AddToggleStep("trigger loading", v => display.Loading = v); } From c3bf6a0287c16682e71a750728c40cfc2a99b06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 May 2021 21:01:37 +0200 Subject: [PATCH 439/708] Remove weird vestigial `Current` reimplementation Has no functional purpose anymore since the changes in the HUD element data binding flow. --- osu.Game/Skinning/LegacyScoreCounter.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 21112aa107..ecb907e601 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; @@ -16,8 +15,6 @@ namespace osu.Game.Skinning protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; - public new Bindable Current { get; } = new Bindable(); - public LegacyScoreCounter(ISkin skin) : base(6) { @@ -26,9 +23,6 @@ namespace osu.Game.Skinning this.skin = skin; - // base class uses int for display, but externally we bind to ScoreProcessor as a double for now. - Current.BindValueChanged(v => base.Current.Value = (int)v.NewValue); - Scale = new Vector2(0.96f); Margin = new MarginPadding(10); } From efb9164658a127093cf39672466bafbb89268e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 May 2021 21:35:12 +0200 Subject: [PATCH 440/708] Restore previous test scene logic --- .../Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs index 34356184a2..6a8a2187f9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; @@ -15,8 +13,6 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinnableAccuracyCounter : SkinnableTestScene { - private IEnumerable accuracyCounters => CreatedDrawables.OfType(); - protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); [Cached] @@ -25,7 +21,8 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUpSteps] public void SetUpSteps() { - AddStep("Create combo counters", () => SetContents(() => new SkinnableAccuracyCounter())); + AddStep("Set initial accuracy", () => scoreProcessor.Accuracy.Value = 1); + AddStep("Create accuracy counters", () => SetContents(() => new SkinnableAccuracyCounter())); } [Test] @@ -33,7 +30,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep(@"Reset all", () => scoreProcessor.Accuracy.Value = 1); - AddStep(@"Hit! :D", () => scoreProcessor.Accuracy.Value -= 0.23); + AddStep(@"Miss :(", () => scoreProcessor.Accuracy.Value -= 0.023); } } } From 67cea6e762f8dd2ff0e886bc2fc1ea6c79c56ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 8 May 2021 21:38:03 +0200 Subject: [PATCH 441/708] Remove explicit binding to accuracy counter from overlay --- osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs | 3 --- osu.Game/Screens/Play/HUDOverlay.cs | 2 -- 2 files changed, 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs index 17c2493d2d..fcb8fca35d 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs @@ -1,15 +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.Bindables; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { public class SkinnableAccuracyCounter : SkinnableDrawable { - public Bindable Current { get; } = new Bindable(); - public SkinnableAccuracyCounter() : base(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter), _ => new DefaultAccuracyCounter()) { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index c887fb78e0..e9b376c433 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -320,8 +320,6 @@ namespace osu.Game.Screens.Play protected virtual void BindScoreProcessor(ScoreProcessor processor) { - AccuracyCounter?.Current.BindTo(processor.Accuracy); - if (HealthDisplay is IHealthDisplay shd) { processor.NewJudgement += judgement => From 8fba655d2e9007449f19b67abf2876ce61656b67 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 21:20:17 +0300 Subject: [PATCH 442/708] Allow changing ruleset during test --- .../TestSceneBeatmapMetadataDisplay.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index f41180acf2..0fbf42da6f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osuTK; @@ -23,7 +24,18 @@ namespace osu.Game.Tests.Visual.SongSelect [Resolved] private BeatmapManager manager { get; set; } - private void createDisplay(Func getBeatmap) + [Resolved] + private RulesetStore rulesets { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + foreach (var ruleset in rulesets.AvailableRulesets) + AddStep($"switch to {ruleset.Name}", () => Ruleset.Value = ruleset); + } + + private void createTest(Func getBeatmap) { AddStep("setup display", () => Child = display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(getRandomMods()), Empty()) { @@ -43,7 +55,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Values("Trial", "Some1's very hardest difficulty")] string version) { - createDisplay(() => CreateWorkingBeatmap(new Beatmap + createTest(() => CreateWorkingBeatmap(new Beatmap { BeatmapInfo = { @@ -60,7 +72,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestRandomFromDatabase() { - createDisplay(() => + createTest(() => { var allBeatmapSets = manager.GetAllUsableBeatmapSets(IncludedDetails.Minimal); if (allBeatmapSets.Count == 0) From 342c5a5938253c961bfc97d2e1239f74ba20f4d9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 May 2021 04:49:40 +0300 Subject: [PATCH 443/708] Add tests to indicate the issue --- .../Visual/Online/TestSceneNewsOverlay.cs | 62 ++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index 6ebe8fcc07..f10b385c61 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -14,21 +17,34 @@ namespace osu.Game.Tests.Visual.Online { private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; - private NewsOverlay news; + private NewsOverlay overlay; [SetUp] - public void SetUp() => Schedule(() => Child = news = new NewsOverlay()); + public void SetUp() => Schedule(() => Child = overlay = new NewsOverlay()); [Test] public void TestRequest() { setUpNewsResponse(responseExample); - AddStep("Show", () => news.Show()); - AddStep("Show article", () => news.ShowArticle("article")); + AddStep("Show", () => overlay.Show()); + AddStep("Show article", () => overlay.ShowArticle("article")); } - private void setUpNewsResponse(GetNewsResponse r) - => AddStep("set up response", () => + [Test] + public void TestCursorRequest() + { + setUpNewsResponse(responseWithCursor, "Set up cursor response"); + AddStep("Show", () => overlay.Show()); + AddAssert("Show More button is visible", () => showMoreButton.Alpha == 1); + setUpNewsResponse(responseWithNoCursor, "Set up no cursor response"); + AddStep("Click Show More", () => showMoreButton.Click()); + AddAssert("Show More button is hidden", () => showMoreButton.Alpha == 0); + } + + private ShowMoreButton showMoreButton => overlay.ChildrenOfType().First(); + + private void setUpNewsResponse(GetNewsResponse r, string testName = "Set up response") + => AddStep(testName, () => { dummyAPI.HandleRequest = request => { @@ -40,7 +56,7 @@ namespace osu.Game.Tests.Visual.Online }; }); - private GetNewsResponse responseExample => new GetNewsResponse + private static GetNewsResponse responseExample => new GetNewsResponse { NewsPosts = new[] { @@ -62,5 +78,37 @@ namespace osu.Game.Tests.Visual.Online } } }; + + private static GetNewsResponse responseWithCursor => new GetNewsResponse + { + NewsPosts = new[] + { + new APINewsPost + { + Title = "This post has an image which starts with \"/\" and has many authors!", + Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + Author = "someone, someone1, someone2, someone3, someone4", + FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", + PublishedAt = DateTimeOffset.Now + } + }, + Cursor = new Cursor() + }; + + private static GetNewsResponse responseWithNoCursor => new GetNewsResponse + { + NewsPosts = new[] + { + new APINewsPost + { + Title = "This post has a full-url image! (HTML entity: &)", + Preview = "boom (HTML entity: &)", + Author = "user (HTML entity: &)", + FirstImage = "https://assets.ppy.sh/artists/88/header.jpg", + PublishedAt = DateTimeOffset.Now + } + }, + Cursor = null + }; } } From dde9fd28e6872e2afe41670861ffb7ec510b74b9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 May 2021 04:57:24 +0300 Subject: [PATCH 444/708] Hide ShowMore button if there's nothing to load --- osu.Game/Overlays/News/Displays/FrontPageDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs index 0f177f151a..a1bc6c650b 100644 --- a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs @@ -100,7 +100,7 @@ namespace osu.Game.Overlays.News.Displays { content.Add(loaded); showMore.IsLoading = false; - showMore.Show(); + showMore.Alpha = lastCursor == null ? 0 : 1; }, (cancellationToken = new CancellationTokenSource()).Token); } From ae2b5a0806c3b0cad6dba614f1273d9d121cf0f6 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 8 May 2021 22:42:14 -0400 Subject: [PATCH 445/708] Actually test that player was exited --- 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 70b1d3ef85..0ac8e01482 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("disable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, false)); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("exit via pause", () => Player.ExitViaPause()); - AddAssert("score not shown", () => !Player.IsScoreShown); + AddAssert("player exited", () => Stack.CurrentScreen == null); } protected override bool AllowFail => true; From 0818deac17110c65ae22005c5616c55e67913847 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 May 2021 06:06:34 +0300 Subject: [PATCH 446/708] Fix potential test scene failure due to showMoreButton not being loaded in time --- osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index f10b385c61..46454879da 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Visual.Online { setUpNewsResponse(responseWithCursor, "Set up cursor response"); AddStep("Show", () => overlay.Show()); + AddUntilStep("Show more button is ready", () => showMoreButton != null); AddAssert("Show More button is visible", () => showMoreButton.Alpha == 1); setUpNewsResponse(responseWithNoCursor, "Set up no cursor response"); AddStep("Click Show More", () => showMoreButton.Click()); From 8868439ce46b042b2eae1bd7fc48614ac30d9197 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 May 2021 06:49:12 +0300 Subject: [PATCH 447/708] Another approach to fix test scene failure --- osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index 46454879da..0be09d1fcc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -36,10 +36,10 @@ namespace osu.Game.Tests.Visual.Online setUpNewsResponse(responseWithCursor, "Set up cursor response"); AddStep("Show", () => overlay.Show()); AddUntilStep("Show more button is ready", () => showMoreButton != null); - AddAssert("Show More button is visible", () => showMoreButton.Alpha == 1); + AddAssert("Show More button is visible", () => showMoreButton?.Alpha == 1); setUpNewsResponse(responseWithNoCursor, "Set up no cursor response"); - AddStep("Click Show More", () => showMoreButton.Click()); - AddAssert("Show More button is hidden", () => showMoreButton.Alpha == 0); + AddStep("Click Show More", () => showMoreButton?.Click()); + AddAssert("Show More button is hidden", () => showMoreButton?.Alpha == 0); } private ShowMoreButton showMoreButton => overlay.ChildrenOfType().First(); From f0c1784d05a56dea2082473ca02063c1721bdad1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 May 2021 09:12:37 +0300 Subject: [PATCH 448/708] Use FirstOrDefault instead of First --- osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index 0be09d1fcc..ba692a7769 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Show More button is hidden", () => showMoreButton?.Alpha == 0); } - private ShowMoreButton showMoreButton => overlay.ChildrenOfType().First(); + private ShowMoreButton showMoreButton => overlay.ChildrenOfType().FirstOrDefault(); private void setUpNewsResponse(GetNewsResponse r, string testName = "Set up response") => AddStep(testName, () => From 879c08e6667024694dde3b8bb095b00185a3f3c3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 May 2021 10:06:36 +0300 Subject: [PATCH 449/708] Use UntilStep instead of Assert to check button visibility --- osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index ba692a7769..93a5b6fc59 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -35,11 +35,10 @@ namespace osu.Game.Tests.Visual.Online { setUpNewsResponse(responseWithCursor, "Set up cursor response"); AddStep("Show", () => overlay.Show()); - AddUntilStep("Show more button is ready", () => showMoreButton != null); - AddAssert("Show More button is visible", () => showMoreButton?.Alpha == 1); + AddUntilStep("Show More button is visible", () => showMoreButton?.Alpha == 1); setUpNewsResponse(responseWithNoCursor, "Set up no cursor response"); AddStep("Click Show More", () => showMoreButton?.Click()); - AddAssert("Show More button is hidden", () => showMoreButton?.Alpha == 0); + AddUntilStep("Show More button is hidden", () => showMoreButton?.Alpha == 0); } private ShowMoreButton showMoreButton => overlay.ChildrenOfType().FirstOrDefault(); From 8964d51de984f3089f5016d184e58b5ba222654e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 9 May 2021 14:10:38 -0700 Subject: [PATCH 450/708] Add ability to sort by source in song select --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 1 + osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 3 +++ osu.Game/Screens/Select/Filter/SortMode.cs | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 5731b1ac2c..643f4131dc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -304,6 +304,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Sort by BPM", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.BPM)); AddStep(@"Sort by Length", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Length)); AddStep(@"Sort by Difficulty", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Difficulty)); + AddStep(@"Sort by Source", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Source)); } [Test] diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index bf045ed612..635f1d8e1e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -82,6 +82,9 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.Difficulty: return compareUsingAggregateMax(otherSet, b => b.StarDifficulty); + + case SortMode.Source: + return string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase); } } diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs index be76fbc3ba..db0e6812b8 100644 --- a/osu.Game/Screens/Select/Filter/SortMode.cs +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -29,6 +29,9 @@ namespace osu.Game.Screens.Select.Filter RankAchieved, [Description("Title")] - Title + Title, + + [Description("Source")] + Source, } } From a21718f1cd89139f684835de8d2dd7d432da2de6 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 9 May 2021 14:26:45 -0700 Subject: [PATCH 451/708] Move source case to a better spot --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 635f1d8e1e..00c2c2cb4a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -71,6 +71,9 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.Author: return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase); + case SortMode.Source: + return string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase); + case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); @@ -82,9 +85,6 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.Difficulty: return compareUsingAggregateMax(otherSet, b => b.StarDifficulty); - - case SortMode.Source: - return string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase); } } From a71e52da4caf53c88b772263d4b7fb89090a34ec Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 9 May 2021 15:39:59 -0700 Subject: [PATCH 452/708] Fix enum ordering after adding source --- osu.Game/Screens/Select/Filter/SortMode.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs index db0e6812b8..18c5d713e1 100644 --- a/osu.Game/Screens/Select/Filter/SortMode.cs +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -28,10 +28,10 @@ namespace osu.Game.Screens.Select.Filter [Description("Rank Achieved")] RankAchieved, - [Description("Title")] - Title, - [Description("Source")] Source, + + [Description("Title")] + Title, } } From ab6239fd5f4545077162a2adec8352c789426cf0 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 10 May 2021 00:51:58 +0200 Subject: [PATCH 453/708] change math for displaying volume "MAX" --- osu.Game/Overlays/Volume/VolumeMeter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index f1f21bec49..a15076581e 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -204,7 +204,7 @@ namespace osu.Game.Overlays.Volume { displayVolume = value; - if (displayVolume > 0.99f) + if (displayVolume >= 0.995f) { text.Text = "MAX"; maxGlow.EffectColour = meterColour.Opacity(2f); From 132bb7832d31e2c30719c65dd4da18d8fbbc2691 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 12:06:33 +0900 Subject: [PATCH 454/708] Fix some regressions when updating test scenes --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 7 ++----- .../Gameplay/TestSceneSkinnableHealthDisplay.cs | 11 +++++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 99facb2731..ebc431a78b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; @@ -21,17 +22,13 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private OsuConfigManager config { get; set; } - [SetUpSteps] - public void SetUpSteps() - { - } - private void create(HealthProcessor healthProcessor) { AddStep("create layer", () => { Child = new HealthProcessorContainer(healthProcessor) { + RelativeSizeAxes = Axes.Both, Child = layer = new FailingLayer() }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index 2d6a6d95c7..4f50613416 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -43,18 +43,21 @@ namespace osu.Game.Tests.Visual.Gameplay { AddRepeatStep(@"decrease hp", delegate { - healthProcessor.Health.Value = 0.08f; + healthProcessor.Health.Value -= 0.08f; }, 10); AddRepeatStep(@"increase hp without flash", delegate { - healthProcessor.Health.Value = 0.1f; + healthProcessor.Health.Value += 0.1f; }, 3); AddRepeatStep(@"increase hp with flash", delegate { - healthProcessor.Health.Value = 0.1f; - healthProcessor.ApplyResult(new JudgementResult(new HitCircle(), new OsuJudgement())); + healthProcessor.Health.Value += 0.1f; + healthProcessor.ApplyResult(new JudgementResult(new HitCircle(), new OsuJudgement()) + { + Type = HitResult.Perfect + }); }, 3); } } From 1bbbe8042099a5b1c3b659b1b899e2970651425c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 12:20:22 +0900 Subject: [PATCH 455/708] Fix missing instances of `HealthProcessor` caching --- .../Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs | 1 + osu.Game/Screens/Play/Player.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 7c65601d50..ac5599cc27 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -18,6 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinEditorMultipleSkins : SkinnableTestScene { + [Cached] private readonly ScoreProcessor scoreProcessor = new ScoreProcessor(); [Cached(typeof(HealthProcessor))] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 396a9f841d..ba34ed4750 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -208,6 +208,8 @@ namespace osu.Game.Screens.Play HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); + dependencies.CacheAs(HealthProcessor); + if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); From 1d38fa29b5a853a2be449ff00e3732359b0fbe00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 12:23:04 +0900 Subject: [PATCH 456/708] Remove unused using statement --- osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index ebc431a78b..fb4c9d713a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; From 0c973feb53f1e76a593994340d522e1afb439e46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 12:34:21 +0900 Subject: [PATCH 457/708] Tidy up test scene --- .../TestSceneBeatmapMetadataDisplay.cs | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 0fbf42da6f..e80c453da9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -35,27 +35,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep($"switch to {ruleset.Name}", () => Ruleset.Value = ruleset); } - private void createTest(Func getBeatmap) - { - AddStep("setup display", () => Child = display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(getRandomMods()), Empty()) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(1.5f), - Alpha = 0f, - }); - - AddStep("fade in", () => display.FadeIn(400, Easing.OutQuint)); - AddToggleStep("trigger loading", v => display.Loading = v); - } - [Test] public void TestLocal([Values("Beatmap", "Some long title and stuff")] string title, [Values("Trial", "Some1's very hardest difficulty")] string version) { - createTest(() => CreateWorkingBeatmap(new Beatmap + showMetadataForBeatmap(() => CreateWorkingBeatmap(new Beatmap { BeatmapInfo = { @@ -72,7 +58,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestRandomFromDatabase() { - createTest(() => + showMetadataForBeatmap(() => { var allBeatmapSets = manager.GetAllUsableBeatmapSets(IncludedDetails.Minimal); if (allBeatmapSets.Count == 0) @@ -85,10 +71,23 @@ namespace osu.Game.Tests.Visual.SongSelect }); } - private IReadOnlyList getRandomMods() => Ruleset.Value.CreateInstance() - .GetAllMods() - .OrderBy(_ => RNG.Next()) - .Take(5) - .ToList(); + private void showMetadataForBeatmap(Func getBeatmap) + { + AddStep("setup display", () => + { + var randomMods = Ruleset.Value.CreateInstance().GetAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList(); + + Child = display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(randomMods), Empty()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.5f), + Alpha = 0f, + }; + }); + + AddStep("fade in", () => display.FadeIn(400, Easing.OutQuint)); + AddToggleStep("trigger loading", v => display.Loading = v); + } } } From 2b90bc4f1f85e35d1610e3e1e993e97dd5df643b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 12:35:23 +0900 Subject: [PATCH 458/708] Remove unnecessary ruleset switching steps --- .../SongSelect/TestSceneBeatmapMetadataDisplay.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index e80c453da9..0fd9197ab4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osuTK; @@ -24,17 +23,6 @@ namespace osu.Game.Tests.Visual.SongSelect [Resolved] private BeatmapManager manager { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - foreach (var ruleset in rulesets.AvailableRulesets) - AddStep($"switch to {ruleset.Name}", () => Ruleset.Value = ruleset); - } - [Test] public void TestLocal([Values("Beatmap", "Some long title and stuff")] string title, From b7acf9de52606a8a3bc87983d5deb096a773e6ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 12:36:56 +0900 Subject: [PATCH 459/708] Make test work without manually clicking things --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 0fd9197ab4..49aec02aa0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -72,10 +72,13 @@ namespace osu.Game.Tests.Visual.SongSelect Scale = new Vector2(1.5f), Alpha = 0f, }; + + display.FadeIn(400, Easing.OutQuint); }); - AddStep("fade in", () => display.FadeIn(400, Easing.OutQuint)); - AddToggleStep("trigger loading", v => display.Loading = v); + AddWaitStep("wait a bit", 5); + + AddStep("finish loading", () => display.Loading = false); } } } From 9ba412d27e9fe2161530a133efe2d74d329cdb8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 12:41:22 +0900 Subject: [PATCH 460/708] Add the osu! logo to the test scene Makes no sense to add a test intended to test visual behaviour with one of the main elements missing. Not sure how you would be able to test the flow with the logo's presence. --- .../SongSelect/TestSceneBeatmapMetadataDisplay.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 49aec02aa0..230822e070 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Menu; using osu.Game.Screens.Play; using osuTK; @@ -65,12 +66,16 @@ namespace osu.Game.Tests.Visual.SongSelect { var randomMods = Ruleset.Value.CreateInstance().GetAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList(); - Child = display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(randomMods), Empty()) + OsuLogo logo = new OsuLogo { Scale = new Vector2(0.15f) }; + + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(1.5f), - Alpha = 0f, + display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(randomMods), logo) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0f, + } }; display.FadeIn(400, Easing.OutQuint); From 35a7226cd82120207014a652567a5e87febc25e2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 10 May 2021 13:41:04 +0900 Subject: [PATCH 461/708] Add newline --- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 2292f64989..6c2571cc28 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -39,7 +39,8 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult judgement) { - if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit) Flash(judgement); + if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit) + Flash(judgement); } protected override void Dispose(bool isDisposing) From 332cb74cad9f3adf3934432c344ce275be3405ee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 10 May 2021 13:58:13 +0900 Subject: [PATCH 462/708] Fix toolbar queuing ruleset sounds --- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index eb235632e8..9ca105ee7f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -69,13 +69,15 @@ namespace osu.Game.Overlays.Toolbar base.LoadComplete(); Current.BindDisabledChanged(disabled => this.FadeColour(disabled ? Color4.Gray : Color4.White, 300), true); - Current.BindValueChanged(_ => moveLineToCurrent(), true); + Current.BindValueChanged(_ => moveLineToCurrent()); + + // Scheduled to allow the button flow layout to be computed before the line position is updated + ScheduleAfterChildren(moveLineToCurrent); } private bool hasInitialPosition; - // Scheduled to allow the flow layout to be computed before the line position is updated - private void moveLineToCurrent() => ScheduleAfterChildren(() => + private void moveLineToCurrent() { if (SelectedTab != null) { @@ -86,7 +88,7 @@ namespace osu.Game.Overlays.Toolbar hasInitialPosition = true; } - }); + } public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; From 301dab1ce8d8eb481a4180f63a7aa213919279c2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 12:55:07 +0300 Subject: [PATCH 463/708] Refactor `StarRatingDisplay` to be mutable with a current bindable --- .../Ranking/TestSceneStarRatingDisplay.cs | 15 +++- .../Ranking/Expanded/StarRatingDisplay.cs | 87 ++++++++++++------- 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs index d0067c3396..a043f506c1 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.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 NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Screens.Ranking.Expanded; @@ -10,8 +12,11 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneStarRatingDisplay : OsuTestScene { - public TestSceneStarRatingDisplay() + [SetUp] + public void SetUp() => Schedule(() => { + StarRatingDisplay changingStarRating; + Child = new FillFlowContainer { Anchor = Anchor.Centre, @@ -25,8 +30,14 @@ namespace osu.Game.Tests.Visual.Ranking new StarRatingDisplay(new StarDifficulty(5.67, 0)), new StarRatingDisplay(new StarDifficulty(6.78, 0)), new StarRatingDisplay(new StarDifficulty(10.11, 0)), + changingStarRating = new StarRatingDisplay(), } }; - } + + Scheduler.AddDelayed(() => + { + changingStarRating.Current.Value = new StarDifficulty(RNG.NextDouble(0, 10), RNG.Next()); + }, 500, true); + }); } } diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index f7e50fdc8a..a1f48fa811 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -3,12 +3,14 @@ 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; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -20,17 +22,29 @@ namespace osu.Game.Screens.Ranking.Expanded /// /// A pill that displays the star rating of a . /// - public class StarRatingDisplay : CompositeDrawable + public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue { - private readonly StarDifficulty difficulty; + private Box background; + private OsuTextFlowContainer textFlow; + + [Resolved] + private OsuColour colours { get; set; } + + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } /// /// Creates a new using an already computed . /// /// The already computed to display the star difficulty of. - public StarRatingDisplay(StarDifficulty starDifficulty) + public StarRatingDisplay(StarDifficulty starDifficulty = default) { - difficulty = starDifficulty; + Current.Value = starDifficulty; } [BackgroundDependencyLoader] @@ -38,15 +52,6 @@ namespace osu.Game.Screens.Ranking.Expanded { AutoSizeAxes = Axes.Both; - var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); - string wholePart = starRatingParts[0]; - string fractionPart = starRatingParts[1]; - string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - - ColourInfo backgroundColour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus - ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); - InternalChildren = new Drawable[] { new CircularContainer @@ -55,10 +60,9 @@ namespace osu.Game.Screens.Ranking.Expanded Masking = true, Children = new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, - Colour = backgroundColour }, } }, @@ -78,32 +82,51 @@ namespace osu.Game.Screens.Ranking.Expanded Icon = FontAwesome.Solid.Star, Colour = Color4.Black }, - new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) + textFlow = 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; - }); - }) + } } } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(difficulty => updateDisplay(difficulty.NewValue), true); + } + + private void updateDisplay(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; + + background.Colour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus + ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) + : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); + + textFlow.Clear(); + + textFlow.AddText($"{wholePart}", s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 14); + s.UseFullGlyphHeight = false; + }); + + textFlow.AddText($"{separator}{fractionPart}", s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 7); + s.UseFullGlyphHeight = false; + }); + } } } From ca772b60b183e33d0c42896f5340088c5f3206b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 18:21:29 +0300 Subject: [PATCH 464/708] Use regular test steps rather than one-time set up and scheduling --- .../Visual/Ranking/TestSceneStarRatingDisplay.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs index a043f506c1..2ff664a0d9 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs @@ -12,12 +12,12 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneStarRatingDisplay : OsuTestScene { - [SetUp] - public void SetUp() => Schedule(() => + [Test] + public void TestDisplay() { - StarRatingDisplay changingStarRating; + StarRatingDisplay changingStarRating = null; - Child = new FillFlowContainer + AddStep("load displays", () => Child = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -32,12 +32,12 @@ namespace osu.Game.Tests.Visual.Ranking new StarRatingDisplay(new StarDifficulty(10.11, 0)), changingStarRating = new StarRatingDisplay(), } - }; + }); - Scheduler.AddDelayed(() => + AddRepeatStep("change bottom rating", () => { changingStarRating.Current.Value = new StarDifficulty(RNG.NextDouble(0, 10), RNG.Next()); - }, 500, true); - }); + }, 10); + } } } From 1c49590ba2e37ca79e03f7de0599e20f8be3a2f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 8 May 2021 21:13:18 +0300 Subject: [PATCH 465/708] Apply further refactoring to star rating display UX-wise --- .../Ranking/Expanded/StarRatingDisplay.cs | 106 +++++++++++++----- 1 file changed, 76 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index a1f48fa811..77c43ad863 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -13,7 +13,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -22,27 +22,50 @@ namespace osu.Game.Screens.Ranking.Expanded /// /// A pill that displays the star rating of a . /// - public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue + public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue { private Box background; - private OsuTextFlowContainer textFlow; + private OsuSpriteText wholePart; + private OsuSpriteText fractionPart; + + private double displayedStarRating; + + protected double DisplayedStarRating + { + get => displayedStarRating; + set + { + displayedStarRating = value; + + var starRatingParts = value.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); + wholePart.Text = starRatingParts[0]; + fractionPart.Text = starRatingParts[1]; + } + } [Resolved] private OsuColour colours { get; set; } - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); - public Bindable Current + public Bindable Current { get => current.Current; set => current.Current = value; } + /// + /// Creates a new without any set, displaying a placeholder until is changed. + /// + public StarRatingDisplay() + { + } + /// /// Creates a new using an already computed . /// /// The already computed to display the star difficulty of. - public StarRatingDisplay(StarDifficulty starDifficulty = default) + public StarRatingDisplay(StarDifficulty starDifficulty) { Current.Value = starDifficulty; } @@ -82,13 +105,40 @@ namespace osu.Game.Screens.Ranking.Expanded Icon = FontAwesome.Solid.Star, Colour = Color4.Black }, - textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) + new FillFlowContainer { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - TextAnchor = Anchor.BottomLeft, + Children = new[] + { + wholePart = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Colour = Color4.Black, + Font = OsuFont.Numeric.With(size: 14, weight: FontWeight.Black), + UseFullGlyphHeight = false, + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Colour = Color4.Black, + Text = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator, + Font = OsuFont.Numeric.With(size: 7, weight: FontWeight.Black), + UseFullGlyphHeight = false, + }, + fractionPart = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Colour = Color4.Black, + Font = OsuFont.Numeric.With(size: 7, weight: FontWeight.Black), + UseFullGlyphHeight = false, + } + } } } } @@ -98,35 +148,31 @@ namespace osu.Game.Screens.Ranking.Expanded protected override void LoadComplete() { base.LoadComplete(); - Current.BindValueChanged(difficulty => updateDisplay(difficulty.NewValue), true); + + Current.BindValueChanged(_ => updateDisplay(), true); + FinishTransforms(true); } - private void updateDisplay(StarDifficulty difficulty) + private void updateDisplay() { - var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); - string wholePart = starRatingParts[0]; - string fractionPart = starRatingParts[1]; - string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + const double duration = 400; + const Easing easing = Easing.OutQuint; - background.Colour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus - ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); + ColourInfo backgroundColour; - textFlow.Clear(); - - textFlow.AddText($"{wholePart}", s => + if (Current.Value == null) + backgroundColour = Color4.SlateGray.Opacity(0.3f); + else { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 14); - s.UseFullGlyphHeight = false; - }); + var rating = Current.Value.Value.DifficultyRating; - textFlow.AddText($"{separator}{fractionPart}", s => - { - s.Colour = Color4.Black; - s.Font = s.Font.With(size: 7); - s.UseFullGlyphHeight = false; - }); + backgroundColour = rating == DifficultyRating.ExpertPlus + ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) + : (ColourInfo)colours.ForDifficultyRating(rating); + } + + background.FadeColour(backgroundColour, duration, easing); + this.TransformTo(nameof(DisplayedStarRating), Current.Value?.Stars ?? 0.0f, duration, easing); } } } From 97e72849af46241de4acbe4e955c5e6a72db9434 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 15:19:27 +0900 Subject: [PATCH 466/708] Fix regressed `HitErrorDisplay` behaviour (and localise binding to meter implementations) --- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 39 +++++++------------ osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 24 ------------ .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 2 +- .../HUD/HitErrorMeters/ColourHitErrorMeter.cs | 2 +- .../Play/HUD/HitErrorMeters/HitErrorMeter.cs | 29 +++++++++++++- 5 files changed, 45 insertions(+), 51 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 1021ac3760..6cefd01aab 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -2,15 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Game.Rulesets.Judgements; -using osu.Framework.Utils; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Catch.Scoring; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Scoring; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Scoring; @@ -20,14 +21,11 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneHitErrorMeter : OsuTestScene { - private BarHitErrorMeter barMeter; - private BarHitErrorMeter barMeter2; - private BarHitErrorMeter barMeter3; - private ColourHitErrorMeter colourMeter; - private ColourHitErrorMeter colourMeter2; - private ColourHitErrorMeter colourMeter3; private HitWindows hitWindows; + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(); + public TestSceneHitErrorMeter() { recreateDisplay(new OsuHitWindows(), 5); @@ -105,40 +103,40 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(barMeter = new BarHitErrorMeter(hitWindows, true) + Add(new BarHitErrorMeter(hitWindows, true) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }); - Add(barMeter2 = new BarHitErrorMeter(hitWindows, false) + Add(new BarHitErrorMeter(hitWindows, false) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }); - Add(barMeter3 = new BarHitErrorMeter(hitWindows, true) + Add(new BarHitErrorMeter(hitWindows, true) { Anchor = Anchor.BottomCentre, Origin = Anchor.CentreLeft, Rotation = 270, }); - Add(colourMeter = new ColourHitErrorMeter(hitWindows) + Add(new ColourHitErrorMeter(hitWindows) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Margin = new MarginPadding { Right = 50 } }); - Add(colourMeter2 = new ColourHitErrorMeter(hitWindows) + Add(new ColourHitErrorMeter(hitWindows) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Left = 50 } }); - Add(colourMeter3 = new ColourHitErrorMeter(hitWindows) + Add(new ColourHitErrorMeter(hitWindows) { Anchor = Anchor.BottomCentre, Origin = Anchor.CentreLeft, @@ -149,18 +147,11 @@ namespace osu.Game.Tests.Visual.Gameplay private void newJudgement(double offset = 0) { - var judgement = new JudgementResult(new HitObject(), new Judgement()) + scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = HitResult.Perfect, - }; - - barMeter.OnNewJudgement(judgement); - barMeter2.OnNewJudgement(judgement); - barMeter3.OnNewJudgement(judgement); - colourMeter.OnNewJudgement(judgement); - colourMeter2.OnNewJudgement(judgement); - colourMeter3.OnNewJudgement(judgement); + }); } } } diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 998a9fb7e7..a24d9c10cb 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -7,7 +7,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD.HitErrorMeters; @@ -22,17 +21,11 @@ namespace osu.Game.Screens.Play.HUD private readonly HitWindows hitWindows; - [Resolved] - private ScoreProcessor processor { get; set; } - public HitErrorDisplay(HitWindows hitWindows) { this.hitWindows = hitWindows; RelativeSizeAxes = Axes.Both; - - if (processor != null) - processor.NewJudgement += onNewJudgement; } [BackgroundDependencyLoader] @@ -47,15 +40,6 @@ namespace osu.Game.Screens.Play.HUD type.BindValueChanged(typeChanged, true); } - private void onNewJudgement(JudgementResult result) - { - if (result.HitObject.HitWindows.WindowFor(HitResult.Miss) == 0) - return; - - foreach (var c in Children) - c.OnNewJudgement(result); - } - private void typeChanged(ValueChangedEvent type) { Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint)); @@ -139,13 +123,5 @@ namespace osu.Game.Screens.Play.HUD Add(display); display.FadeInFromZero(fade_duration, Easing.OutQuint); } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (processor != null) - processor.NewJudgement -= onNewJudgement; - } } } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 3b24c8cc9e..c5a21ade03 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -214,7 +214,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private const int max_concurrent_judgements = 50; - public override void OnNewJudgement(JudgementResult judgement) + protected override void OnNewJudgement(JudgementResult judgement) { if (!judgement.IsHit) return; diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 657235bfd4..465439cf19 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters InternalChild = judgementsFlow = new JudgementFlow(); } - public override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(GetColourForHitResult(HitWindows.ResultFor(judgement.TimeOffset))); + protected override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(GetColourForHitResult(HitWindows.ResultFor(judgement.TimeOffset))); private class JudgementFlow : FillFlowContainer { diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index b3edfdedec..8b53a9676d 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -14,6 +14,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { protected readonly HitWindows HitWindows; + [Resolved] + private ScoreProcessor processor { get; set; } + [Resolved] private OsuColour colours { get; set; } @@ -22,7 +25,23 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters HitWindows = hitWindows; } - public abstract void OnNewJudgement(JudgementResult judgement); + protected override void LoadComplete() + { + base.LoadComplete(); + + if (processor != null) + processor.NewJudgement += onNewJudgement; + } + + private void onNewJudgement(JudgementResult result) + { + if (result.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0) + return; + + OnNewJudgement(result); + } + + protected abstract void OnNewJudgement(JudgementResult judgement); protected Color4 GetColourForHitResult(HitResult result) { @@ -47,5 +66,13 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters return colours.BlueLight; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (processor != null) + processor.NewJudgement -= onNewJudgement; + } } } From afc9a1bf235ef9c3026292254d788ce98072158d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 10 May 2021 10:16:52 +0300 Subject: [PATCH 467/708] Remove rolling support and apply few adjustments --- .../Ranking/Expanded/StarRatingDisplay.cs | 81 +++++++------------ 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 77c43ad863..422833555f 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -13,7 +13,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; @@ -25,23 +25,7 @@ namespace osu.Game.Screens.Ranking.Expanded public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue { private Box background; - private OsuSpriteText wholePart; - private OsuSpriteText fractionPart; - - private double displayedStarRating; - - protected double DisplayedStarRating - { - get => displayedStarRating; - set - { - displayedStarRating = value; - - var starRatingParts = value.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); - wholePart.Text = starRatingParts[0]; - fractionPart.Text = starRatingParts[1]; - } - } + private OsuTextFlowContainer textFlow; [Resolved] private OsuColour colours { get; set; } @@ -105,40 +89,13 @@ namespace osu.Game.Screens.Ranking.Expanded Icon = FontAwesome.Solid.Star, Colour = Color4.Black }, - new FillFlowContainer + textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.Numeric.With(weight: FontWeight.Black)) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Children = new[] - { - wholePart = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Colour = Color4.Black, - Font = OsuFont.Numeric.With(size: 14, weight: FontWeight.Black), - UseFullGlyphHeight = false, - }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Colour = Color4.Black, - Text = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator, - Font = OsuFont.Numeric.With(size: 7, weight: FontWeight.Black), - UseFullGlyphHeight = false, - }, - fractionPart = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Colour = Color4.Black, - Font = OsuFont.Numeric.With(size: 7, weight: FontWeight.Black), - UseFullGlyphHeight = false, - } - } + TextAnchor = Anchor.BottomLeft, } } } @@ -158,21 +115,39 @@ namespace osu.Game.Screens.Ranking.Expanded const double duration = 400; const Easing easing = Easing.OutQuint; - ColourInfo backgroundColour; + double stars = Current.Value?.Stars ?? 0.00f; + + var starRatingParts = stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); + string wholePart = starRatingParts[0]; + string fractionPart = starRatingParts[1]; + string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; if (Current.Value == null) - backgroundColour = Color4.SlateGray.Opacity(0.3f); + background.FadeColour(Color4.SlateGray.Opacity(0.3f)); else { var rating = Current.Value.Value.DifficultyRating; - backgroundColour = rating == DifficultyRating.ExpertPlus + background.FadeColour(rating == DifficultyRating.ExpertPlus ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(rating); + : (ColourInfo)colours.ForDifficultyRating(rating), duration, easing); } - background.FadeColour(backgroundColour, duration, easing); - this.TransformTo(nameof(DisplayedStarRating), Current.Value?.Stars ?? 0.0f, duration, easing); + textFlow.Clear(); + + textFlow.AddText($"{wholePart}", s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 14); + s.UseFullGlyphHeight = false; + }); + + textFlow.AddText($"{separator}{fractionPart}", s => + { + s.Colour = Color4.Black; + s.Font = s.Font.With(size: 7); + s.UseFullGlyphHeight = false; + }); } } } From fa872858b592278b7672c16fbd840825d8ac7a24 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 10 May 2021 16:40:06 +0900 Subject: [PATCH 468/708] Remove unnecessary check --- osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index 8b53a9676d..37e9ea43c5 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -29,8 +29,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { base.LoadComplete(); - if (processor != null) - processor.NewJudgement += onNewJudgement; + processor.NewJudgement += onNewJudgement; } private void onNewJudgement(JudgementResult result) From 1b701adfef5a20c1cd008449fe4a4eed503f2be6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 18:15:39 +0900 Subject: [PATCH 469/708] Add score/health processors to fill in default values --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 1953fdc8fe..a000204062 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -25,6 +26,16 @@ namespace osu.Game.Skinning.Editor private const float component_display_scale = 0.8f; + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor + { + Combo = { Value = 727 }, + TotalScore = { Value = 1337377 } + }; + + [Cached(typeof(HealthProcessor))] + private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); + public SkinComponentToolbox(float height) : base("Components", height) { From bca5bee72eb22df8f9885c8962efe2a9125417fa Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 10 May 2021 19:28:32 +0700 Subject: [PATCH 470/708] remove duplicate CreateSpriteText in OsuMarkdownTextFlowContainer --- .../Containers/Markdown/OsuMarkdownTextFlowContainer.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 1550b8401d..c3527fa99a 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; using osu.Game.Overlays; namespace osu.Game.Graphics.Containers.Markdown @@ -16,11 +15,6 @@ namespace osu.Game.Graphics.Containers.Markdown [Resolved] private OverlayColourProvider colourProvider { get; set; } - protected override SpriteText CreateSpriteText() => new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14), - }; - protected override void AddLinkText(string text, LinkInline linkInline) => AddDrawable(new OsuMarkdownLinkText(text, linkInline)); From f1aa47f6df50456892f2c7da4d4281f5e288714f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 23:15:38 +0900 Subject: [PATCH 471/708] 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 80b1c5b52f..8c24df5c2e 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 29189781a7..3eb5050e1a 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 c4eb7aefba..f00e20a66e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 7c8dd91674481b209d383cda463b423534f7722e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 09:29:15 +0900 Subject: [PATCH 472/708] Rename test to better match tested class --- ...apDifficultyManagerTest.cs => BeatmapDifficultyCacheTest.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Beatmaps/{BeatmapDifficultyManagerTest.cs => BeatmapDifficultyCacheTest.cs} (98%) diff --git a/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs b/osu.Game.Tests/Beatmaps/BeatmapDifficultyCacheTest.cs similarity index 98% rename from osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs rename to osu.Game.Tests/Beatmaps/BeatmapDifficultyCacheTest.cs index 70503bec7a..d407c0663f 100644 --- a/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs +++ b/osu.Game.Tests/Beatmaps/BeatmapDifficultyCacheTest.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Tests.Beatmaps { [TestFixture] - public class BeatmapDifficultyManagerTest + public class BeatmapDifficultyCacheTest { [Test] public void TestKeyEqualsWithDifferentModInstances() From 32f7691349dd449593a8664ac191dcb84d56a8b2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 11:24:35 +0900 Subject: [PATCH 473/708] Fix token failure preventing base.LoadAsyncComplete() --- osu.Game/Screens/Play/SubmittingPlayer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index b64d835c58..79c1d1a274 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -33,9 +33,8 @@ namespace osu.Game.Screens.Play protected override void LoadAsyncComplete() { - if (!handleTokenRetrieval()) return; - base.LoadAsyncComplete(); + handleTokenRetrieval(); } private bool handleTokenRetrieval() From 6db9e26d48a490b6c3bf9912877bd8f22220b492 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 11:24:55 +0900 Subject: [PATCH 474/708] Fix score submission failures with autoplay --- osu.Game/Screens/Play/SubmittingPlayer.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 79c1d1a274..ece1a589cd 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.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 JetBrains.Annotations; using osu.Framework.Allocation; @@ -9,6 +10,7 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; namespace osu.Game.Screens.Play @@ -42,6 +44,12 @@ namespace osu.Game.Screens.Play // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. var tcs = new TaskCompletionSource(); + if (DrawableRuleset.HasReplayLoaded.Value || Mods.Value.Any(m => m is ModAutoplay)) + { + handleTokenFailure(new InvalidOperationException("Replay loaded.")); + return false; + } + if (!api.IsLoggedIn) { handleTokenFailure(new InvalidOperationException("API is not online.")); From 8c9390dc75fd819b65f164c141bbf02420efec29 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 11:33:21 +0900 Subject: [PATCH 475/708] Remove replay condition --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index ece1a589cd..68c6282fe0 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Play // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. var tcs = new TaskCompletionSource(); - if (DrawableRuleset.HasReplayLoaded.Value || Mods.Value.Any(m => m is ModAutoplay)) + if (Mods.Value.Any(m => m is ModAutoplay)) { handleTokenFailure(new InvalidOperationException("Replay loaded.")); return false; From 0f00ee8640e332453bd0131824360b594fe685a0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 11:35:07 +0900 Subject: [PATCH 476/708] Change failure text Although this is not visible anywhere. --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 68c6282fe0..d5ad87c70c 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play if (Mods.Value.Any(m => m is ModAutoplay)) { - handleTokenFailure(new InvalidOperationException("Replay loaded.")); + handleTokenFailure(new InvalidOperationException("Autoplay loaded.")); return false; } From 4bee8c23f0f8b6d502d31d877cda12261790d881 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 10 May 2021 21:40:29 -0700 Subject: [PATCH 477/708] Fix idle tracker not accounting global actions --- osu.Game/Input/IdleTracker.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index 2d6a21d1cf..f3d531cf6c 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -6,13 +6,14 @@ using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Input.Bindings; namespace osu.Game.Input { /// /// Track whether the end-user is in an idle state, based on their last interaction with the game. /// - public class IdleTracker : Component, IKeyBindingHandler, IHandleGlobalKeyboardInput + public class IdleTracker : Component, IKeyBindingHandler, IKeyBindingHandler, IHandleGlobalKeyboardInput { private readonly double timeToIdle; @@ -58,6 +59,10 @@ namespace osu.Game.Input public void OnReleased(PlatformAction action) => updateLastInteractionTime(); + public bool OnPressed(GlobalAction action) => updateLastInteractionTime(); + + public void OnReleased(GlobalAction action) => updateLastInteractionTime(); + protected override bool Handle(UIEvent e) { switch (e) From 713c169332dacbd7b63b0948ade3290c614004dd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 16:08:25 +0900 Subject: [PATCH 478/708] Fix mania crashing on playing samples after skin change --- .../Drawables/DrawableManiaHitObject.cs | 6 +++++ osu.Game.Rulesets.Mania/UI/Column.cs | 27 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 1550faee50..003646d654 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -6,6 +6,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Mania.UI; @@ -24,6 +25,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved(canBeNull: true)] private ManiaPlayfield playfield { get; set; } + /// + /// Gets the samples that are played by this object during gameplay. + /// + public ISampleInfo[] GetGameplaySamples() => Samples.Samples; + protected override float SamplePlaybackPosition { get diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index d2a9b69b60..fcbbd8a01c 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -27,6 +27,15 @@ namespace osu.Game.Rulesets.Mania.UI public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; + /// + /// For hitsounds played by this (i.e. not as a result of hitting a hitobject), + /// a certain number of samples are allowed to be played concurrently so that it feels better when spam-pressing the key. + /// + /// + /// + /// + private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY; + /// /// The index of this column as part of the whole playfield. /// @@ -38,6 +47,7 @@ namespace osu.Game.Rulesets.Mania.UI internal readonly Container TopLevelContainer; private readonly DrawablePool hitExplosionPool; private readonly OrderedHitPolicy hitPolicy; + private readonly SkinnableSound[] hitSounds; public Container UnderlayElements => HitObjectArea.UnderlayElements; @@ -64,6 +74,11 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both }, background, + new Container + { + RelativeSizeAxes = Axes.Both, + ChildrenEnumerable = hitSounds = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray() + }, TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } }; @@ -120,6 +135,8 @@ namespace osu.Game.Rulesets.Mania.UI HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); } + private int nextHitSound; + public bool OnPressed(ManiaAction action) { if (action != Action.Value) @@ -131,7 +148,15 @@ namespace osu.Game.Rulesets.Mania.UI HitObjectContainer.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? HitObjectContainer.Objects.LastOrDefault(); - nextObject?.PlaySamples(); + if (nextObject is DrawableManiaHitObject maniaObject) + { + var hitSound = hitSounds[nextHitSound]; + + hitSound.Samples = maniaObject.GetGameplaySamples(); + hitSound.Play(); + + nextHitSound = (nextHitSound + 1) % max_concurrent_hitsounds; + } return true; } From b9ab9342faeee26c54903beb326109698c8525b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 May 2021 15:16:16 +0900 Subject: [PATCH 479/708] Setup basics to allow extracting serializable content from skinnable `Drawable`s --- osu.Game/Extensions/DrawableExtensions.cs | 3 ++ osu.Game/Screens/Play/HUD/ISkinnableInfo.cs | 32 ++++++++++++++ .../HUD/SkinnableElementTargetContainer.cs | 11 +++++ .../Screens/Play/HUD/StoredSkinnableInfo.cs | 43 +++++++++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 38 +++++++--------- .../Skinning/Editor/SkinBlueprintContainer.cs | 13 +++++- osu.Game/Skinning/ISkin.cs | 2 + 7 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/ISkinnableInfo.cs create mode 100644 osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs create mode 100644 osu.Game/Screens/Play/HUD/StoredSkinnableInfo.cs diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index a8de3f6407..3d2ffe0ea1 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Threading; +using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Extensions @@ -43,5 +44,7 @@ namespace osu.Game.Extensions /// 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); + + public static StoredSkinnableInfo CreateSerialisedInformation(this Drawable component) => new StoredSkinnableInfo(component); } } diff --git a/osu.Game/Screens/Play/HUD/ISkinnableInfo.cs b/osu.Game/Screens/Play/HUD/ISkinnableInfo.cs new file mode 100644 index 0000000000..0e571b5cb4 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ISkinnableInfo.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Game.IO.Serialization; +using osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + public interface ISkinnableInfo : IJsonSerializable + { + public Type Type { get; set; } + + public Type Target { get; set; } + + public Vector2 Position { get; set; } + + public float Rotation { get; set; } + + public Vector2 Scale { get; set; } + + public Anchor Anchor { get; set; } + } + + /// + /// A container which supports skinnable components being added to it. + /// + public interface ISkinnableTarget + { + } +} diff --git a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs new file mode 100644 index 0000000000..7ba32e2d46 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.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.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Play.HUD +{ + public class SkinnableElementTargetContainer : Container, ISkinnableTarget + { + } +} diff --git a/osu.Game/Screens/Play/HUD/StoredSkinnableInfo.cs b/osu.Game/Screens/Play/HUD/StoredSkinnableInfo.cs new file mode 100644 index 0000000000..e4ec035e68 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/StoredSkinnableInfo.cs @@ -0,0 +1,43 @@ +// 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 osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// Serialised information governing custom changes to an . + /// + public class StoredSkinnableInfo : ISkinnableInfo + { + public StoredSkinnableInfo(Drawable component) + { + Type = component.GetType(); + + var target = component.Parent as ISkinnableTarget + // todo: this is temporary until we serialise the default layouts out of SkinnableDrawables. + ?? component.Parent?.Parent as ISkinnableTarget; + + Target = target?.GetType(); + + Position = component.Position; + Rotation = component.Rotation; + Scale = component.Scale; + Anchor = component.Anchor; + } + + public Type Type { get; set; } + + public Type Target { get; set; } + + public Vector2 Position { get; set; } + + public float Rotation { get; set; } + + public Vector2 Scale { get; set; } + + public Anchor Anchor { get; set; } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 6ddaa338cc..13449e3a2b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -91,16 +91,16 @@ namespace osu.Game.Screens.Play { new Drawable[] { - new Container + new MainHUDElements { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - HealthDisplay = CreateHealthDisplay(), - AccuracyCounter = CreateAccuracyCounter(), - ScoreCounter = CreateScoreCounter(), - CreateComboCounter(), - CreateHitErrorDisplayOverlay(), + HealthDisplay = new SkinnableHealthDisplay(), + AccuracyCounter = new SkinnableAccuracyCounter(), + ScoreCounter = new SkinnableScoreCounter(), + new SkinnableComboCounter(), + new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows), } }, }, @@ -263,48 +263,38 @@ namespace osu.Game.Screens.Play Progress.BindDrawableRuleset(drawableRuleset); } - protected SkinnableAccuracyCounter CreateAccuracyCounter() => new SkinnableAccuracyCounter(); - - protected SkinnableScoreCounter CreateScoreCounter() => new SkinnableScoreCounter(); - - protected SkinnableComboCounter CreateComboCounter() => new SkinnableComboCounter(); - - protected SkinnableHealthDisplay CreateHealthDisplay() => new SkinnableHealthDisplay(); - - protected virtual FailingLayer CreateFailingLayer() => new FailingLayer + protected FailingLayer CreateFailingLayer() => new FailingLayer { ShowHealth = { BindTarget = ShowHealthbar } }; - protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay + protected KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, }; - protected virtual SongProgress CreateProgress() => new SongProgress + protected SongProgress CreateProgress() => new SongProgress { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, }; - protected virtual HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton + protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, }; - protected virtual ModDisplay CreateModsContainer() => new ModDisplay + protected ModDisplay CreateModsContainer() => new ModDisplay { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, }; - protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(drawableRuleset?.FirstAvailableHitWindows); - - protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); + protected PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); public bool OnPressed(GlobalAction action) { @@ -347,5 +337,9 @@ namespace osu.Game.Screens.Play break; } } + + public class MainHUDElements : SkinnableElementTargetContainer + { + } } } diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 35e93d9aff..a6f60afd90 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.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 System.IO; using System.Linq; +using Newtonsoft.Json; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Extensions; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components; @@ -27,7 +30,15 @@ namespace osu.Game.Skinning.Editor private void checkForComponents() { - foreach (var c in target.ChildrenOfType().ToArray()) AddBlueprintFor(c); + ISkinnableComponent[] skinnableComponents = target.ChildrenOfType().ToArray(); + + // todo: the OfType() call can be removed with better IDrawable support. + string json = JsonConvert.SerializeObject(skinnableComponents.OfType().Select(d => d.CreateSerialisedInformation()), new JsonSerializerSettings { Formatting = Formatting.Indented }); + + File.WriteAllText("/Users/Dean/json-out.json", json); + + foreach (var c in skinnableComponents) + 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. diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 73f7cf6d39..9fa781fa5d 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -57,5 +57,7 @@ namespace osu.Game.Skinning /// A matching value boxed in an , or null if unavailable. [CanBeNull] IBindable GetConfig(TLookup lookup); + + // IEnumerable ComponentInfo { get; } } } From 67ea4a7e97bce85d0f5f861d021b7113809bc16b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 18:18:29 +0900 Subject: [PATCH 480/708] Read from skin config --- .../Play/HUD/SkinnableElementTargetContainer.cs | 17 +++++++++++++++-- osu.Game/Skinning/ISkin.cs | 4 ++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs index 7ba32e2d46..8da7950c57 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs @@ -1,11 +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.Containers; +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class SkinnableElementTargetContainer : Container, ISkinnableTarget + public class SkinnableElementTargetContainer : SkinReloadableDrawable, ISkinnableTarget { + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + var loadable = skin.GetConfig>(this.GetType()); + + ClearInternal(); + if (loadable != null) + LoadComponentsAsync(loadable.Value, AddRangeInternal); + } } } diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 9fa781fa5d..5371893882 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -57,7 +59,5 @@ namespace osu.Game.Skinning /// A matching value boxed in an , or null if unavailable. [CanBeNull] IBindable GetConfig(TLookup lookup); - - // IEnumerable ComponentInfo { get; } } } From 95a8f21ab26065f1bccc4d8cfa2ad83051da27a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 19:13:38 +0900 Subject: [PATCH 481/708] Add the concept of skinnable target containers and mark a basic one for HUD elements --- osu.Game/Extensions/DrawableExtensions.cs | 11 +++++- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 3 +- osu.Game/Screens/Play/HUD/ISkinnableInfo.cs | 10 +---- osu.Game/Screens/Play/HUD/ISkinnableTarget.cs | 15 +++++++ .../HUD/SkinnableElementTargetContainer.cs | 14 ++++--- ...toredSkinnableInfo.cs => SkinnableInfo.cs} | 32 +++++++++++---- osu.Game/Screens/Play/HUDOverlay.cs | 37 +++++++++++++----- osu.Game/Skinning/DefaultSkin.cs | 39 ++++++++++++++++++- osu.Game/Skinning/ISkin.cs | 2 - osu.Game/Skinning/SkinnableTarget.cs | 10 +++++ osu.Game/Skinning/SkinnableTargetComponent.cs | 17 ++++++++ 11 files changed, 154 insertions(+), 36 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/ISkinnableTarget.cs rename osu.Game/Screens/Play/HUD/{StoredSkinnableInfo.cs => SkinnableInfo.cs} (51%) create mode 100644 osu.Game/Skinning/SkinnableTarget.cs create mode 100644 osu.Game/Skinning/SkinnableTargetComponent.cs diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 3d2ffe0ea1..289b305c27 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -45,6 +45,15 @@ namespace osu.Game.Extensions public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) => drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta); - public static StoredSkinnableInfo CreateSerialisedInformation(this Drawable component) => new StoredSkinnableInfo(component); + public static SkinnableInfo CreateSerialisedInformation(this Drawable component) => new SkinnableInfo(component); + + public static void ApplySerialisedInformation(this Drawable component, ISkinnableInfo info) + { + // todo: can probably make this better via deserialisation directly using a common interface. + component.Position = info.Position; + component.Rotation = info.Rotation; + component.Scale = info.Scale; + component.Anchor = info.Anchor; + } } } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 2e84c9c97d..0e147f9238 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -13,13 +13,12 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { - public class BarHitErrorMeter : HitErrorMeter, ISkinnableComponent + public class BarHitErrorMeter : HitErrorMeter { private readonly Anchor alignment; diff --git a/osu.Game/Screens/Play/HUD/ISkinnableInfo.cs b/osu.Game/Screens/Play/HUD/ISkinnableInfo.cs index 0e571b5cb4..fc4e9680a8 100644 --- a/osu.Game/Screens/Play/HUD/ISkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/ISkinnableInfo.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Game.IO.Serialization; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Play.HUD @@ -12,7 +13,7 @@ namespace osu.Game.Screens.Play.HUD { public Type Type { get; set; } - public Type Target { get; set; } + public SkinnableTarget? Target { get; set; } public Vector2 Position { get; set; } @@ -22,11 +23,4 @@ namespace osu.Game.Screens.Play.HUD public Anchor Anchor { get; set; } } - - /// - /// A container which supports skinnable components being added to it. - /// - public interface ISkinnableTarget - { - } } diff --git a/osu.Game/Screens/Play/HUD/ISkinnableTarget.cs b/osu.Game/Screens/Play/HUD/ISkinnableTarget.cs new file mode 100644 index 0000000000..6cd269333a --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ISkinnableTarget.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.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// A container which supports skinnable components being added to it. + /// + public interface ISkinnableTarget + { + public SkinnableTarget Target { get; } + } +} diff --git a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs index 8da7950c57..bd82533823 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs @@ -1,24 +1,28 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using osu.Framework.Graphics; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { public class SkinnableElementTargetContainer : SkinReloadableDrawable, ISkinnableTarget { + public SkinnableTarget Target { get; } + + public SkinnableElementTargetContainer(SkinnableTarget target) + { + Target = target; + } + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); - var loadable = skin.GetConfig>(this.GetType()); + var loadable = skin.GetDrawableComponent(new SkinnableTargetComponent(Target)); ClearInternal(); if (loadable != null) - LoadComponentsAsync(loadable.Value, AddRangeInternal); + LoadComponentAsync(loadable, AddInternal); } } } diff --git a/osu.Game/Screens/Play/HUD/StoredSkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs similarity index 51% rename from osu.Game/Screens/Play/HUD/StoredSkinnableInfo.cs rename to osu.Game/Screens/Play/HUD/SkinnableInfo.cs index e4ec035e68..5b1f840efd 100644 --- a/osu.Game/Screens/Play/HUD/StoredSkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -2,7 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using osu.Framework.Graphics; +using osu.Game.Extensions; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Play.HUD @@ -10,17 +14,20 @@ namespace osu.Game.Screens.Play.HUD /// /// Serialised information governing custom changes to an . /// - public class StoredSkinnableInfo : ISkinnableInfo + [Serializable] + public class SkinnableInfo : ISkinnableInfo { - public StoredSkinnableInfo(Drawable component) + public SkinnableInfo() + { + } + + public SkinnableInfo(Drawable component) { Type = component.GetType(); - var target = component.Parent as ISkinnableTarget - // todo: this is temporary until we serialise the default layouts out of SkinnableDrawables. - ?? component.Parent?.Parent as ISkinnableTarget; + ISkinnableTarget target = component.Parent as ISkinnableTarget; - Target = target?.GetType(); + Target = target?.Target; Position = component.Position; Rotation = component.Rotation; @@ -30,7 +37,8 @@ namespace osu.Game.Screens.Play.HUD public Type Type { get; set; } - public Type Target { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public SkinnableTarget? Target { get; set; } public Vector2 Position { get; set; } @@ -40,4 +48,14 @@ namespace osu.Game.Screens.Play.HUD public Anchor Anchor { get; set; } } + + public static class SkinnableInfoExtensions + { + public static Drawable CreateInstance(this ISkinnableInfo info) + { + Drawable d = (Drawable)Activator.CreateInstance(info.Type); + d.ApplySerialisedInformation(info); + return d; + } + } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 13449e3a2b..1a12ca4cbd 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -91,16 +91,37 @@ namespace osu.Game.Screens.Play { new Drawable[] { - new MainHUDElements + new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - HealthDisplay = new SkinnableHealthDisplay(), - AccuracyCounter = new SkinnableAccuracyCounter(), - ScoreCounter = new SkinnableScoreCounter(), - new SkinnableComboCounter(), - new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows), + new SkinnableElementTargetContainer(SkinnableTarget.MainHUDComponents) + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Children = new Drawable[] + { + // remaining cross-dependencies need tidying. + // kept to ensure non-null, but hidden for testing. + HealthDisplay = new SkinnableHealthDisplay(), + AccuracyCounter = new SkinnableAccuracyCounter(), + ScoreCounter = new SkinnableScoreCounter(), + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + // still need to be migrated; a bit more involved. + new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows), + } + }, } }, }, @@ -337,9 +358,5 @@ namespace osu.Game.Screens.Play break; } } - - public class MainHUDElements : SkinnableElementTargetContainer - { - } } } diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 0b3f5f3cde..48da841c45 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -2,12 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.IO; +using System.Linq; +using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.Screens.Play.HUD; using osuTK.Graphics; namespace osu.Game.Skinning @@ -20,7 +25,29 @@ namespace osu.Game.Skinning Configuration = new DefaultSkinConfiguration(); } - public override Drawable GetDrawableComponent(ISkinComponent component) => null; + public override Drawable GetDrawableComponent(ISkinComponent component) + { + switch (component) + { + case SkinnableTargetComponent target: + switch (target.Target) + { + case SkinnableTarget.MainHUDComponents: + var infos = JsonConvert.DeserializeObject>(File.ReadAllText("/Users/Dean/json-out.json")).Where(i => i.Target == SkinnableTarget.MainHUDComponents); + + var container = new SkinnableTargetWrapper(target.Target) + { + ChildrenEnumerable = infos.Select(i => i.CreateInstance()) + }; + + return container; + } + + break; + } + + return null; + } public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; @@ -45,4 +72,14 @@ namespace osu.Game.Skinning return null; } } + + public class SkinnableTargetWrapper : Container, ISkinnableTarget + { + public SkinnableTarget Target { get; } + + public SkinnableTargetWrapper(SkinnableTarget target) + { + Target = target; + } + } } diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index 5371893882..73f7cf6d39 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; using JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; diff --git a/osu.Game/Skinning/SkinnableTarget.cs b/osu.Game/Skinning/SkinnableTarget.cs new file mode 100644 index 0000000000..7b1eae126c --- /dev/null +++ b/osu.Game/Skinning/SkinnableTarget.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.Skinning +{ + public enum SkinnableTarget + { + MainHUDComponents + } +} diff --git a/osu.Game/Skinning/SkinnableTargetComponent.cs b/osu.Game/Skinning/SkinnableTargetComponent.cs new file mode 100644 index 0000000000..a17aafe6e7 --- /dev/null +++ b/osu.Game/Skinning/SkinnableTargetComponent.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Skinning +{ + public class SkinnableTargetComponent : ISkinComponent + { + public readonly SkinnableTarget Target; + + public string LookupName => Target.ToString(); + + public SkinnableTargetComponent(SkinnableTarget target) + { + Target = target; + } + } +} From 95a497e9df7313ed1e6ae0513c1a623fba63c7cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 15:31:14 +0900 Subject: [PATCH 482/708] Remove unused interface class for simplicity --- osu.Game/Extensions/DrawableExtensions.cs | 2 +- osu.Game/Screens/Play/HUD/ISkinnableInfo.cs | 26 --------------------- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 12 ++++------ 3 files changed, 6 insertions(+), 34 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/ISkinnableInfo.cs diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 289b305c27..e84c9e5a98 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -47,7 +47,7 @@ namespace osu.Game.Extensions public static SkinnableInfo CreateSerialisedInformation(this Drawable component) => new SkinnableInfo(component); - public static void ApplySerialisedInformation(this Drawable component, ISkinnableInfo info) + public static void ApplySerialisedInformation(this Drawable component, SkinnableInfo info) { // todo: can probably make this better via deserialisation directly using a common interface. component.Position = info.Position; diff --git a/osu.Game/Screens/Play/HUD/ISkinnableInfo.cs b/osu.Game/Screens/Play/HUD/ISkinnableInfo.cs deleted file mode 100644 index fc4e9680a8..0000000000 --- a/osu.Game/Screens/Play/HUD/ISkinnableInfo.cs +++ /dev/null @@ -1,26 +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.Graphics; -using osu.Game.IO.Serialization; -using osu.Game.Skinning; -using osuTK; - -namespace osu.Game.Screens.Play.HUD -{ - public interface ISkinnableInfo : IJsonSerializable - { - public Type Type { get; set; } - - public SkinnableTarget? Target { get; set; } - - public Vector2 Position { get; set; } - - public float Rotation { get; set; } - - public Vector2 Scale { get; set; } - - public Anchor Anchor { get; set; } - } -} diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 5b1f840efd..2edc99781f 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Framework.Graphics; using osu.Game.Extensions; +using osu.Game.IO.Serialization; using osu.Game.Skinning; using osuTK; @@ -15,7 +16,7 @@ namespace osu.Game.Screens.Play.HUD /// Serialised information governing custom changes to an . /// [Serializable] - public class SkinnableInfo : ISkinnableInfo + public class SkinnableInfo : IJsonSerializable { public SkinnableInfo() { @@ -47,14 +48,11 @@ namespace osu.Game.Screens.Play.HUD public Vector2 Scale { get; set; } public Anchor Anchor { get; set; } - } - public static class SkinnableInfoExtensions - { - public static Drawable CreateInstance(this ISkinnableInfo info) + public Drawable CreateInstance() { - Drawable d = (Drawable)Activator.CreateInstance(info.Type); - d.ApplySerialisedInformation(info); + Drawable d = (Drawable)Activator.CreateInstance(Type); + d.ApplySerialisedInformation(this); return d; } } From df72656aa1238a337550ed1b114a45300c2455a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 15:40:33 +0900 Subject: [PATCH 483/708] Remove downwards dependency from `HUDOverlay` to `HealthDisplay` --- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 13 +++++++++++-- osu.Game/Screens/Play/HUDOverlay.cs | 3 --- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 6c2571cc28..e0ddde026b 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -16,6 +17,8 @@ namespace osu.Game.Screens.Play.HUD /// public abstract class HealthDisplay : Container { + private Bindable showHealthbar; + [Resolved] protected HealthProcessor HealthProcessor { get; private set; } @@ -29,12 +32,18 @@ namespace osu.Game.Screens.Play.HUD { } - [BackgroundDependencyLoader] - private void load() + [BackgroundDependencyLoader(true)] + private void load(HUDOverlay hud) { Current.BindTo(HealthProcessor.Health); HealthProcessor.NewJudgement += onNewJudgement; + + if (hud != null) + { + showHealthbar = hud.ShowHealthbar.GetBoundCopy(); + showHealthbar.BindValueChanged(healthBar => this.FadeTo(healthBar.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true); + } } private void onNewJudgement(JudgementResult judgement) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1a12ca4cbd..7220365673 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -36,7 +36,6 @@ namespace osu.Game.Screens.Play public readonly KeyCounterDisplay KeyCounter; public readonly SkinnableScoreCounter ScoreCounter; public readonly SkinnableAccuracyCounter AccuracyCounter; - public readonly SkinnableHealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; @@ -108,7 +107,6 @@ namespace osu.Game.Screens.Play { // remaining cross-dependencies need tidying. // kept to ensure non-null, but hidden for testing. - HealthDisplay = new SkinnableHealthDisplay(), AccuracyCounter = new SkinnableAccuracyCounter(), ScoreCounter = new SkinnableScoreCounter(), } @@ -206,7 +204,6 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - ShowHealthbar.BindValueChanged(healthBar => HealthDisplay.FadeTo(healthBar.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING), true); ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING))); IsBreakTime.BindValueChanged(_ => updateVisibility()); From 1742ee89e0680662cd5d12348ddceef4c5df2be9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 22:32:56 +0900 Subject: [PATCH 484/708] Fix incorrect xmldoc for `DeleteFile` --- osu.Game/Database/ArchiveModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index dbeaebb1cd..e0f80d2743 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -472,7 +472,7 @@ namespace osu.Game.Database } /// - /// Delete new file. + /// Delete an existing file. /// /// The item to operate on. /// The existing file to be deleted. From 6a88b8888b1be641d203ed598998b7f0e985c410 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 22:33:45 +0900 Subject: [PATCH 485/708] Add basic support for child serialisation --- osu.Game/Extensions/DrawableExtensions.cs | 7 ++++ osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 37 ++++++++++++++-------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index e84c9e5a98..6837a802f2 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Threading; using osu.Game.Screens.Play.HUD; @@ -54,6 +55,12 @@ namespace osu.Game.Extensions component.Rotation = info.Rotation; component.Scale = info.Scale; component.Anchor = info.Anchor; + + if (component is Container container) + { + foreach (var child in info.Children) + container.Add(child.CreateInstance()); + } } } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 2edc99781f..ec4eaa0e59 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Extensions; using osu.Game.IO.Serialization; using osu.Game.Skinning; @@ -18,6 +21,21 @@ namespace osu.Game.Screens.Play.HUD [Serializable] public class SkinnableInfo : IJsonSerializable { + public Type Type { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public SkinnableTarget? Target { get; set; } + + public Vector2 Position { get; set; } + + public float Rotation { get; set; } + + public Vector2 Scale { get; set; } + + public Anchor Anchor { get; set; } + + public List Children { get; } = new List(); + public SkinnableInfo() { } @@ -34,21 +52,14 @@ namespace osu.Game.Screens.Play.HUD Rotation = component.Rotation; Scale = component.Scale; Anchor = component.Anchor; + + if (component is Container container) + { + foreach (var child in container.Children.OfType().OfType()) + Children.Add(child.CreateSerialisedInformation()); + } } - public Type Type { get; set; } - - [JsonConverter(typeof(StringEnumConverter))] - public SkinnableTarget? Target { get; set; } - - public Vector2 Position { get; set; } - - public float Rotation { get; set; } - - public Vector2 Scale { get; set; } - - public Anchor Anchor { get; set; } - public Drawable CreateInstance() { Drawable d = (Drawable)Activator.CreateInstance(Type); From b54eb56169e08757e85abe78b1113e7425108717 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 22:34:24 +0900 Subject: [PATCH 486/708] Move new judgement binding to `LoadComplete` to ensure containers are loaded --- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index e0ddde026b..e18a28eded 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -37,8 +37,6 @@ namespace osu.Game.Screens.Play.HUD { Current.BindTo(HealthProcessor.Health); - HealthProcessor.NewJudgement += onNewJudgement; - if (hud != null) { showHealthbar = hud.ShowHealthbar.GetBoundCopy(); @@ -46,6 +44,13 @@ namespace osu.Game.Screens.Play.HUD } } + protected override void LoadComplete() + { + base.LoadComplete(); + + HealthProcessor.NewJudgement += onNewJudgement; + } + private void onNewJudgement(JudgementResult judgement) { if (judgement.IsHit && judgement.Type != HitResult.IgnoreHit) From 004798d61d44d88114acfd7c2861378261eab107 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 22:36:20 +0900 Subject: [PATCH 487/708] Update Legacy components to not require skin in ctor --- .../Legacy/LegacyCatchComboCounter.cs | 4 ++-- .../Skinning/Legacy/LegacySpinner.cs | 4 ++-- .../Legacy/OsuLegacySkinTransformer.cs | 2 +- .../Visual/Gameplay/TestSceneSkinEditor.cs | 14 ++++++++++++- .../Screens/Play/HUD/LegacyComboCounter.cs | 4 ++-- .../HUD/SkinnableElementTargetContainer.cs | 15 ++++++++++--- osu.Game/Skinning/LegacyAccuracyCounter.cs | 11 +++++----- osu.Game/Skinning/LegacyHealthDisplay.cs | 21 ++++++++----------- osu.Game/Skinning/LegacyRollingCounter.cs | 7 ++----- osu.Game/Skinning/LegacyScoreCounter.cs | 12 +++++------ osu.Game/Skinning/LegacySkin.cs | 6 +++--- osu.Game/Skinning/LegacySpriteText.cs | 14 ++++++++++--- 12 files changed, 68 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs index 28ee7bd813..33c3867f5a 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy InternalChildren = new Drawable[] { - explosion = new LegacyRollingCounter(skin, LegacyFont.Combo) + explosion = new LegacyRollingCounter(LegacyFont.Combo) { Alpha = 0.65f, Blending = BlendingParameters.Additive, @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy Origin = Anchor.Centre, Scale = new Vector2(1.5f), }, - counter = new LegacyRollingCounter(skin, LegacyFont.Combo) + counter = new LegacyRollingCounter(LegacyFont.Combo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 7eb6898abc..959589620b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Scale = new Vector2(SPRITE_SCALE), Y = SPINNER_TOP_OFFSET + 115, }, - bonusCounter = new LegacySpriteText(source, LegacyFont.Score) + bonusCounter = new LegacySpriteText(LegacyFont.Score) { Alpha = 0f, Anchor = Anchor.TopCentre, @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Scale = new Vector2(SPRITE_SCALE), Position = new Vector2(-87, 445 + spm_hide_offset), }, - spmCounter = new LegacySpriteText(source, LegacyFont.Score) + spmCounter = new LegacySpriteText(LegacyFont.Score) { Anchor = Anchor.TopCentre, Origin = Anchor.TopRight, diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index ffe238c507..88302ebc57 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (!this.HasFont(LegacyFont.HitCircle)) return null; - return new LegacySpriteText(Source, LegacyFont.HitCircle) + return new LegacySpriteText(LegacyFont.HitCircle) { // stable applies a blanket 0.8x scale to hitcircle fonts Scale = new Vector2(0.8f), diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index c53ac42d12..b0edc0dd68 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; using osu.Game.Skinning.Editor; namespace osu.Game.Tests.Visual.Gameplay @@ -14,12 +16,22 @@ namespace osu.Game.Tests.Visual.Gameplay { private SkinEditor skinEditor; + [Resolved] + private SkinManager skinManager { get; set; } + [SetUpSteps] public override void SetUpSteps() { + AddStep("set empty legacy skin", () => + { + var imported = skinManager.Import(new SkinInfo { Name = "test skin" }).Result; + + skinManager.CurrentSkinInfo.Value = imported; + }); + base.SetUpSteps(); - AddStep("add editor overlay", () => + AddStep("reload skin editor", () => { skinEditor?.Expire(); Player.ScaleTo(SkinEditorOverlay.VISIBLE_TARGET_SCALE); diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 73305ac93e..2565faf423 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -84,13 +84,13 @@ namespace osu.Game.Screens.Play.HUD { InternalChildren = new[] { - popOutCount = new LegacySpriteText(skin, LegacyFont.Combo) + popOutCount = new LegacySpriteText(LegacyFont.Combo) { Alpha = 0, Margin = new MarginPadding(0.05f), Blending = BlendingParameters.Additive, }, - displayedCountSpriteText = new LegacySpriteText(skin, LegacyFont.Combo) + displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo) { Alpha = 0, }, diff --git a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs index bd82533823..f1b282119b 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs @@ -1,12 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; +using osu.Game.Extensions; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { public class SkinnableElementTargetContainer : SkinReloadableDrawable, ISkinnableTarget { + private SkinnableTargetWrapper content; + public SkinnableTarget Target { get; } public SkinnableElementTargetContainer(SkinnableTarget target) @@ -14,15 +19,19 @@ namespace osu.Game.Screens.Play.HUD Target = target; } + public IEnumerable CreateSerialisedChildren() => + content.Select(d => d.CreateSerialisedInformation()); + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); - var loadable = skin.GetDrawableComponent(new SkinnableTargetComponent(Target)); + content = skin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetWrapper; ClearInternal(); - if (loadable != null) - LoadComponentAsync(loadable, AddInternal); + + if (content != null) + LoadComponentAsync(content, AddInternal); } } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 3efcd5555e..e2fe01efe4 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -12,23 +12,22 @@ namespace osu.Game.Skinning { public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableComponent { - private readonly ISkin skin; + [Resolved] + private ISkinSource skin { get; set; } - public LegacyAccuracyCounter(ISkin skin) + public LegacyAccuracyCounter() { Anchor = Anchor.TopRight; Origin = Anchor.TopRight; Scale = new Vector2(0.6f); Margin = new MarginPadding(10); - - this.skin = skin; } [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(skin, LegacyFont.Score) + protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -38,7 +37,7 @@ namespace osu.Game.Skinning { base.Update(); - if (hud?.ScoreCounter.Drawable is LegacyScoreCounter score) + if (hud?.ScoreCounter?.Drawable is LegacyScoreCounter score) { // for now align with the score counter. eventually this will be user customisable. Y = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index c1979efbc2..717ca62da7 100644 --- a/osu.Game/Skinning/LegacyHealthDisplay.cs +++ b/osu.Game/Skinning/LegacyHealthDisplay.cs @@ -20,7 +20,9 @@ namespace osu.Game.Skinning { private const double epic_cutoff = 0.5; - private readonly Skin skin; + [Resolved] + private ISkinSource skin { get; set; } + private LegacyHealthPiece fill; private LegacyHealthPiece marker; @@ -28,11 +30,6 @@ namespace osu.Game.Skinning private bool isNewStyle; - public LegacyHealthDisplay(Skin skin) - { - this.skin = skin; - } - [BackgroundDependencyLoader] private void load() { @@ -79,7 +76,7 @@ namespace osu.Game.Skinning protected override void Flash(JudgementResult result) => marker.Flash(result); - private static Texture getTexture(Skin skin, string name) => skin.GetTexture($"scorebar-{name}"); + private static Texture getTexture(ISkinSource skin, string name) => skin.GetTexture($"scorebar-{name}"); private static Color4 getFillColour(double hp) { @@ -98,7 +95,7 @@ namespace osu.Game.Skinning private readonly Texture dangerTexture; private readonly Texture superDangerTexture; - public LegacyOldStyleMarker(Skin skin) + public LegacyOldStyleMarker(ISkinSource skin) { normalTexture = getTexture(skin, "ki"); dangerTexture = getTexture(skin, "kidanger"); @@ -129,9 +126,9 @@ namespace osu.Game.Skinning public class LegacyNewStyleMarker : LegacyMarker { - private readonly Skin skin; + private readonly ISkinSource skin; - public LegacyNewStyleMarker(Skin skin) + public LegacyNewStyleMarker(ISkinSource skin) { this.skin = skin; } @@ -153,7 +150,7 @@ namespace osu.Game.Skinning internal class LegacyOldStyleFill : LegacyHealthPiece { - public LegacyOldStyleFill(Skin skin) + public LegacyOldStyleFill(ISkinSource skin) { // required for sizing correctly.. var firstFrame = getTexture(skin, "colour-0"); @@ -176,7 +173,7 @@ namespace osu.Game.Skinning internal class LegacyNewStyleFill : LegacyHealthPiece { - public LegacyNewStyleFill(Skin skin) + public LegacyNewStyleFill(ISkinSource skin) { InternalChild = new Sprite { diff --git a/osu.Game/Skinning/LegacyRollingCounter.cs b/osu.Game/Skinning/LegacyRollingCounter.cs index 0261db0e64..b531ae1e6f 100644 --- a/osu.Game/Skinning/LegacyRollingCounter.cs +++ b/osu.Game/Skinning/LegacyRollingCounter.cs @@ -12,7 +12,6 @@ namespace osu.Game.Skinning /// public class LegacyRollingCounter : RollingCounter { - private readonly ISkin skin; private readonly LegacyFont font; protected override bool IsRollingProportional => true; @@ -20,11 +19,9 @@ namespace osu.Game.Skinning /// /// Creates a new . /// - /// The from which to get counter number sprites. /// The legacy font to use for the counter. - public LegacyRollingCounter(ISkin skin, LegacyFont font) + public LegacyRollingCounter(LegacyFont font) { - this.skin = skin; this.font = font; } @@ -33,6 +30,6 @@ namespace osu.Game.Skinning return Math.Abs(newValue - currentValue) * 75.0; } - protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(skin, font); + protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(font); } } diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index ecb907e601..0696d2bedd 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; @@ -10,24 +11,23 @@ namespace osu.Game.Skinning { public class LegacyScoreCounter : GameplayScoreCounter, ISkinnableComponent { - private readonly ISkin skin; - protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; - public LegacyScoreCounter(ISkin skin) + [Resolved] + private ISkinSource skin { get; set; } + + public LegacyScoreCounter() : base(6) { Anchor = Anchor.TopRight; Origin = Anchor.TopRight; - this.skin = skin; - Scale = new Vector2(0.96f); Margin = new MarginPadding(10); } - protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(skin, LegacyFont.Score) + protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(LegacyFont.Score) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index eae3b69233..70f8d1d882 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -334,13 +334,13 @@ namespace osu.Game.Skinning return new LegacyComboCounter(); case HUDSkinComponents.ScoreCounter: - return new LegacyScoreCounter(this); + return new LegacyScoreCounter(); case HUDSkinComponents.AccuracyCounter: - return new LegacyAccuracyCounter(this); + return new LegacyAccuracyCounter(); case HUDSkinComponents.HealthDisplay: - return new LegacyHealthDisplay(this); + return new LegacyHealthDisplay(); } return null; diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index c55400e219..7895fcccca 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Text; using osu.Game.Graphics.Sprites; @@ -9,19 +10,26 @@ using osuTK; namespace osu.Game.Skinning { - public class LegacySpriteText : OsuSpriteText + public sealed class LegacySpriteText : OsuSpriteText { - private readonly LegacyGlyphStore glyphStore; + private readonly LegacyFont font; + + private LegacyGlyphStore glyphStore; protected override char FixedWidthReferenceCharacter => '5'; protected override char[] FixedWidthExcludeCharacters => new[] { ',', '.', '%', 'x' }; - public LegacySpriteText(ISkin skin, LegacyFont font) + public LegacySpriteText(LegacyFont font) { + this.font = font; Shadow = false; UseFullGlyphHeight = false; + } + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { Font = new FontUsage(skin.GetFontPrefix(font), 1, fixedWidth: true); Spacing = new Vector2(-skin.GetFontOverlap(font), 0); From b248b2e5e3bf8b62ea94b9e1d5eb85e3809d448e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 22:43:48 +0900 Subject: [PATCH 488/708] Hook up full save/load flow --- osu.Game/Screens/Play/HUD/ISkinnableTarget.cs | 15 ---- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Skinning/DefaultSkin.cs | 42 +--------- .../Skinning/Editor/SkinBlueprintContainer.cs | 8 -- osu.Game/Skinning/Editor/SkinEditor.cs | 43 +++++++++- osu.Game/Skinning/ISkinnableTarget.cs | 4 +- osu.Game/Skinning/LegacySkin.cs | 26 +++++- osu.Game/Skinning/Skin.cs | 81 ++++++++++++++++++- osu.Game/Skinning/SkinManager.cs | 32 ++++++++ 9 files changed, 181 insertions(+), 72 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/ISkinnableTarget.cs diff --git a/osu.Game/Screens/Play/HUD/ISkinnableTarget.cs b/osu.Game/Screens/Play/HUD/ISkinnableTarget.cs deleted file mode 100644 index 6cd269333a..0000000000 --- a/osu.Game/Screens/Play/HUD/ISkinnableTarget.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Skinning; - -namespace osu.Game.Screens.Play.HUD -{ - /// - /// A container which supports skinnable components being added to it. - /// - public interface ISkinnableTarget - { - public SkinnableTarget Target { get; } - } -} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 7220365673..30b735d28a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -22,7 +22,7 @@ using osuTK; namespace osu.Game.Screens.Play { [Cached] - public class HUDOverlay : Container, IKeyBindingHandler, IDefaultSkinnableTarget + public class HUDOverlay : Container, IKeyBindingHandler { public const float FADE_DURATION = 300; diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 48da841c45..bfb5b8ef56 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -2,17 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.IO; -using System.Linq; -using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; -using osu.Game.Screens.Play.HUD; using osuTK.Graphics; namespace osu.Game.Skinning @@ -20,35 +14,11 @@ namespace osu.Game.Skinning public class DefaultSkin : Skin { public DefaultSkin() - : base(SkinInfo.Default) + : base(SkinInfo.Default, null) { Configuration = new DefaultSkinConfiguration(); } - public override Drawable GetDrawableComponent(ISkinComponent component) - { - switch (component) - { - case SkinnableTargetComponent target: - switch (target.Target) - { - case SkinnableTarget.MainHUDComponents: - var infos = JsonConvert.DeserializeObject>(File.ReadAllText("/Users/Dean/json-out.json")).Where(i => i.Target == SkinnableTarget.MainHUDComponents); - - var container = new SkinnableTargetWrapper(target.Target) - { - ChildrenEnumerable = infos.Select(i => i.CreateInstance()) - }; - - return container; - } - - break; - } - - return null; - } - public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; public override ISample GetSample(ISampleInfo sampleInfo) => null; @@ -72,14 +42,4 @@ namespace osu.Game.Skinning return null; } } - - public class SkinnableTargetWrapper : Container, ISkinnableTarget - { - public SkinnableTarget Target { get; } - - public SkinnableTargetWrapper(SkinnableTarget target) - { - Target = target; - } - } } diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index a6f60afd90..45e541abf0 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.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 System.IO; using System.Linq; -using Newtonsoft.Json; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Game.Extensions; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components; @@ -32,11 +29,6 @@ namespace osu.Game.Skinning.Editor { ISkinnableComponent[] skinnableComponents = target.ChildrenOfType().ToArray(); - // todo: the OfType() call can be removed with better IDrawable support. - string json = JsonConvert.SerializeObject(skinnableComponents.OfType().Select(d => d.CreateSerialisedInformation()), new JsonSerializerSettings { Formatting = Formatting.Indented }); - - File.WriteAllText("/Users/Dean/json-out.json", json); - foreach (var c in skinnableComponents) AddBlueprintFor(c); diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 18a8b220df..30ee3097d2 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -11,6 +11,8 @@ using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning.Editor { @@ -24,6 +26,9 @@ namespace osu.Game.Skinning.Editor protected override bool StartHidden => true; + [Resolved] + private SkinManager skins { get; set; } + public SkinEditor(Drawable target) { this.target = target; @@ -53,7 +58,24 @@ namespace osu.Game.Skinning.Editor Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RequestPlacement = placeComponent - } + }, + new TriangleButton + { + Text = "Save Changes", + Width = 140, + Height = 50, + Padding = new MarginPadding + { + Top = 10, + Left = 10, + }, + Margin = new MarginPadding + { + Right = 10, + Bottom = 10, + }, + Action = save, + }, } }; @@ -71,7 +93,7 @@ namespace osu.Game.Skinning.Editor var targetContainer = target.ChildrenOfType().FirstOrDefault(); - targetContainer?.Add(instance); + (targetContainer as Container)?.Add(instance); } protected override void LoadComplete() @@ -80,6 +102,23 @@ namespace osu.Game.Skinning.Editor Show(); } + private void save() + { + var currentSkin = skins.CurrentSkin.Value; + + var legacySkin = currentSkin as LegacySkin; + + if (legacySkin == null) + return; + + SkinnableElementTargetContainer[] targetContainers = target.ChildrenOfType().ToArray(); + + foreach (var t in targetContainers) + legacySkin.UpdateDrawableTarget(t); + + skins.Save(skins.CurrentSkin.Value); + } + protected override bool OnHover(HoverEvent e) => true; protected override bool OnMouseDown(MouseDownEvent e) => true; diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 607e89fdec..90e48c8bba 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -2,14 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; namespace osu.Game.Skinning { /// /// Denotes a container which can house s. /// - public interface ISkinnableTarget : IContainerCollection + public interface ISkinnableTarget : IDrawable { + public SkinnableTarget Target { get; } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 70f8d1d882..fc03ce909c 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -58,7 +58,7 @@ namespace osu.Game.Skinning } protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore storage, [CanBeNull] IStorageResourceProvider resources, string filename) - : base(skin) + : base(skin, resources) { using (var stream = storage?.GetStream(filename)) { @@ -321,8 +321,32 @@ namespace osu.Game.Skinning public override Drawable GetDrawableComponent(ISkinComponent component) { + if (base.GetDrawableComponent(component) is Drawable c) + return c; + switch (component) { + case SkinnableTargetComponent target: + + switch (target.Target) + { + case SkinnableTarget.MainHUDComponents: + return new SkinnableTargetWrapper(target.Target) + { + RelativeSizeAxes = Axes.Both, + Children = new[] + { + // TODO: these should fallback to the osu!classic skin. + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ComboCounter)) ?? new DefaultComboCounter(), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)) ?? new DefaultScoreCounter(), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(), + } + }; + } + + return null; + case HUDSkinComponent hudComponent: { if (!this.HasFont(LegacyFont.Score)) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 6d1bce2cb1..03ecabc886 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -2,12 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.IO; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning { @@ -15,9 +22,13 @@ namespace osu.Game.Skinning { public readonly SkinInfo SkinInfo; + private readonly IStorageResourceProvider resources; + public SkinConfiguration Configuration { get; protected set; } - public abstract Drawable GetDrawableComponent(ISkinComponent componentName); + public IDictionary DrawableComponentInfo => drawableComponentInfo; + + private readonly Dictionary drawableComponentInfo = new Dictionary(); public abstract ISample GetSample(ISampleInfo sampleInfo); @@ -27,9 +38,62 @@ namespace osu.Game.Skinning public abstract IBindable GetConfig(TLookup lookup); - protected Skin(SkinInfo skin) + protected Skin(SkinInfo skin, IStorageResourceProvider resources) { SkinInfo = skin; + + // may be null for default skin. + this.resources = resources; + } + + public void UpdateDrawableTarget(SkinnableElementTargetContainer targetContainer) + { + DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSerialisedChildren().ToArray(); + } + + public virtual Drawable GetDrawableComponent(ISkinComponent component) + { + switch (component) + { + case SkinnableTargetComponent target: + + var skinnableTarget = target.Target; + + if (!DrawableComponentInfo.TryGetValue(skinnableTarget, out var skinnableInfo)) + { + switch (skinnableTarget) + { + case SkinnableTarget.MainHUDComponents: + + // skininfo files may be null for default skin. + var fileInfo = SkinInfo.Files?.FirstOrDefault(f => f.Filename == $"{skinnableTarget}.json"); + + if (fileInfo == null) + return null; + + var bytes = resources?.Files.Get(fileInfo.FileInfo.StoragePath); + + if (bytes == null) + return null; + + string jsonContent = Encoding.UTF8.GetString(bytes); + + DrawableComponentInfo[skinnableTarget] = skinnableInfo = + JsonConvert.DeserializeObject>(jsonContent).Where(i => i.Target == SkinnableTarget.MainHUDComponents).ToArray(); + break; + } + } + + if (skinnableInfo == null) + return null; + + return new SkinnableTargetWrapper(skinnableTarget) + { + ChildrenEnumerable = skinnableInfo.Select(i => i.CreateInstance()) + }; + } + + return null; } #region Disposal @@ -58,4 +122,17 @@ namespace osu.Game.Skinning #endregion } + + public class SkinnableTargetWrapper : Container, IDefaultSkinnableTarget + { + // this is just here to implement the interface and allow a silly parent lookup to work (where the target is not the direct parent for reasons). + // TODO: need to fix this. + public SkinnableTarget Target { get; } + + public SkinnableTargetWrapper(SkinnableTarget target) + { + Target = target; + RelativeSizeAxes = Axes.Both; + } + } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ac4d63159a..f9e22c9c89 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Expressions; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -17,6 +19,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; @@ -158,6 +161,35 @@ namespace osu.Game.Skinning return new LegacySkin(skinInfo, this); } + public void Save(Skin skin) + { + // some skins don't support saving just yet. + // eventually we will want to create a copy of the skin to allow for customisation. + if (skin.SkinInfo.Files == null) + return; + + foreach (var drawableInfo in skin.DrawableComponentInfo) + { + // todo: the OfType() call can be removed with better IDrawable support. + string json = JsonConvert.SerializeObject(drawableInfo.Value, new JsonSerializerSettings { Formatting = Formatting.Indented }); + + using (var streamContent = new MemoryStream(Encoding.UTF8.GetBytes(json))) + { + string filename = $"{drawableInfo.Key}.json"; + + var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename); + + if (oldFile != null) + ReplaceFile(skin.SkinInfo, oldFile, streamContent, oldFile.Filename); + else + AddFile(skin.SkinInfo, streamContent, filename); + + Logger.Log($"Saving out {filename} with {json.Length} bytes"); + Logger.Log(json); + } + } + } + /// /// Perform a lookup query on available s. /// From 1bb3c609aeec1c773aac2a7a28f4c21d52674052 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 11:00:57 +0900 Subject: [PATCH 489/708] Centralise implementation of cancel button logic --- .../UserInterface/DangerousTriangleButton.cs | 18 ++++++++++++++++++ osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 11 +---------- .../KeyBinding/KeyBindingsSubsection.cs | 9 ++------- 3 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/DangerousTriangleButton.cs diff --git a/osu.Game/Graphics/UserInterface/DangerousTriangleButton.cs b/osu.Game/Graphics/UserInterface/DangerousTriangleButton.cs new file mode 100644 index 0000000000..89a4c28c8c --- /dev/null +++ b/osu.Game/Graphics/UserInterface/DangerousTriangleButton.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; + +namespace osu.Game.Graphics.UserInterface +{ + public class DangerousTriangleButton : TriangleButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.PinkDark; + Triangles.ColourDark = colours.PinkDarker; + Triangles.ColourLight = colours.Pink; + } + } +} diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 300fce962a..43942d2d52 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -339,22 +339,13 @@ namespace osu.Game.Overlays.KeyBinding } } - public class ClearButton : TriangleButton + public class ClearButton : DangerousTriangleButton { public ClearButton() { Text = "Clear"; Size = new Vector2(80, 20); } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BackgroundColour = colours.Pink; - - Triangles.ColourDark = colours.PinkDark; - Triangles.ColourLight = colours.PinkLight; - } } public class KeyButton : Container diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index d784b7aec9..707176e63e 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -11,7 +11,6 @@ using osu.Game.Input; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osuTK; -using osu.Game.Graphics; namespace osu.Game.Overlays.KeyBinding { @@ -55,10 +54,10 @@ namespace osu.Game.Overlays.KeyBinding } } - public class ResetButton : TriangleButton + public class ResetButton : DangerousTriangleButton { [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { Text = "Reset all bindings in section"; RelativeSizeAxes = Axes.X; @@ -66,10 +65,6 @@ namespace osu.Game.Overlays.KeyBinding Height = 20; Content.CornerRadius = 5; - - BackgroundColour = colours.PinkDark; - Triangles.ColourDark = colours.PinkDarker; - Triangles.ColourLight = colours.Pink; } } } From c957293ec3a580875f7961bcde4e47bfd20c2a27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 11:54:45 +0900 Subject: [PATCH 490/708] Load json from disk store at skin construction for now This allows for easier mutation without worrying about changes being re-read from disk unexpectedly. --- osu.Game/Skinning/Skin.cs | 62 ++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 03ecabc886..34571ec688 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -44,6 +44,32 @@ namespace osu.Game.Skinning // may be null for default skin. this.resources = resources; + + // we may want to move this to some kind of async operation in the future. + foreach (SkinnableTarget skinnableTarget in Enum.GetValues(typeof(SkinnableTarget))) + { + string filename = $"{skinnableTarget}.json"; + + // skininfo files may be null for default skin. + var fileInfo = SkinInfo.Files?.FirstOrDefault(f => f.Filename == filename); + + if (fileInfo == null) + continue; + + var bytes = resources?.Files.Get(fileInfo.FileInfo.StoragePath); + + if (bytes == null) + continue; + + string jsonContent = Encoding.UTF8.GetString(bytes); + + DrawableComponentInfo[skinnableTarget] = JsonConvert.DeserializeObject>(jsonContent).ToArray(); + } + } + + public void ResetDrawableTarget(SkinnableElementTargetContainer targetContainer) + { + DrawableComponentInfo.Remove(targetContainer.Target); } public void UpdateDrawableTarget(SkinnableElementTargetContainer targetContainer) @@ -60,34 +86,9 @@ namespace osu.Game.Skinning var skinnableTarget = target.Target; if (!DrawableComponentInfo.TryGetValue(skinnableTarget, out var skinnableInfo)) - { - switch (skinnableTarget) - { - case SkinnableTarget.MainHUDComponents: - - // skininfo files may be null for default skin. - var fileInfo = SkinInfo.Files?.FirstOrDefault(f => f.Filename == $"{skinnableTarget}.json"); - - if (fileInfo == null) - return null; - - var bytes = resources?.Files.Get(fileInfo.FileInfo.StoragePath); - - if (bytes == null) - return null; - - string jsonContent = Encoding.UTF8.GetString(bytes); - - DrawableComponentInfo[skinnableTarget] = skinnableInfo = - JsonConvert.DeserializeObject>(jsonContent).Where(i => i.Target == SkinnableTarget.MainHUDComponents).ToArray(); - break; - } - } - - if (skinnableInfo == null) return null; - return new SkinnableTargetWrapper(skinnableTarget) + return new SkinnableTargetWrapper { ChildrenEnumerable = skinnableInfo.Select(i => i.CreateInstance()) }; @@ -123,15 +124,10 @@ namespace osu.Game.Skinning #endregion } - public class SkinnableTargetWrapper : Container, IDefaultSkinnableTarget + public class SkinnableTargetWrapper : Container, ISkinnableComponent { - // this is just here to implement the interface and allow a silly parent lookup to work (where the target is not the direct parent for reasons). - // TODO: need to fix this. - public SkinnableTarget Target { get; } - - public SkinnableTargetWrapper(SkinnableTarget target) + public SkinnableTargetWrapper() { - Target = target; RelativeSizeAxes = Axes.Both; } } From 4769a95b4994b032f64e446272ffed0051230f43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 11:56:14 +0900 Subject: [PATCH 491/708] Fix encapsulation and remove target lookup overhead --- .../HUD/SkinnableElementTargetContainer.cs | 23 ++++++++++++++----- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 9 -------- osu.Game/Skinning/IDefaultSkinnableTarget.cs | 12 ---------- osu.Game/Skinning/ISkinnableTarget.cs | 12 +++++++++- osu.Game/Skinning/LegacySkin.cs | 3 +-- 5 files changed, 29 insertions(+), 30 deletions(-) delete mode 100644 osu.Game/Skinning/IDefaultSkinnableTarget.cs diff --git a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs index f1b282119b..79a0db1751 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics; using osu.Game.Extensions; using osu.Game.Skinning; @@ -19,6 +20,21 @@ namespace osu.Game.Screens.Play.HUD Target = target; } + public void Reload() + { + content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetWrapper; + + ClearInternal(); + + if (content != null) + LoadComponentAsync(content, AddInternal); + } + + public void Add(Drawable drawable) + { + content.Add(drawable); + } + public IEnumerable CreateSerialisedChildren() => content.Select(d => d.CreateSerialisedInformation()); @@ -26,12 +42,7 @@ namespace osu.Game.Screens.Play.HUD { base.SkinChanged(skin, allowFallback); - content = skin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetWrapper; - - ClearInternal(); - - if (content != null) - LoadComponentAsync(content, AddInternal); + Reload(); } } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index ec4eaa0e59..abeb96f647 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Extensions; @@ -23,9 +21,6 @@ namespace osu.Game.Screens.Play.HUD { public Type Type { get; set; } - [JsonConverter(typeof(StringEnumConverter))] - public SkinnableTarget? Target { get; set; } - public Vector2 Position { get; set; } public float Rotation { get; set; } @@ -44,10 +39,6 @@ namespace osu.Game.Screens.Play.HUD { Type = component.GetType(); - ISkinnableTarget target = component.Parent as ISkinnableTarget; - - Target = target?.Target; - Position = component.Position; Rotation = component.Rotation; Scale = component.Scale; diff --git a/osu.Game/Skinning/IDefaultSkinnableTarget.cs b/osu.Game/Skinning/IDefaultSkinnableTarget.cs deleted file mode 100644 index 24fb454af8..0000000000 --- a/osu.Game/Skinning/IDefaultSkinnableTarget.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Skinning -{ - /// - /// The default placement location for new s. - /// - public interface IDefaultSkinnableTarget : ISkinnableTarget - { - } -} diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 90e48c8bba..2379fd4247 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -8,8 +8,18 @@ namespace osu.Game.Skinning /// /// Denotes a container which can house s. /// - public interface ISkinnableTarget : IDrawable + public interface ISkinnableTarget { public SkinnableTarget Target { get; } + + /// + /// Reload this target from the current skin. + /// + public void Reload(); + + /// + /// Add the provided item to this target. + /// + public void Add(Drawable drawable); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fc03ce909c..3ffd2245c2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -331,9 +331,8 @@ namespace osu.Game.Skinning switch (target.Target) { case SkinnableTarget.MainHUDComponents: - return new SkinnableTargetWrapper(target.Target) + return new SkinnableTargetWrapper { - RelativeSizeAxes = Axes.Both, Children = new[] { // TODO: these should fallback to the osu!classic skin. From 81902ad6a688f2e143d06234006027889d8808be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 11:57:12 +0900 Subject: [PATCH 492/708] Add the ability to revert all skin changes --- osu.Game/Skinning/Editor/SkinEditor.cs | 66 +++++++++++++++++++++----- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 30ee3097d2..6b7d289284 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; +using osuTK; namespace osu.Game.Skinning.Editor { @@ -20,7 +21,7 @@ namespace osu.Game.Skinning.Editor { public const double TRANSITION_DURATION = 500; - private readonly Drawable target; + private readonly Drawable targetScreen; private OsuTextFlowContainer headerText; @@ -29,9 +30,9 @@ namespace osu.Game.Skinning.Editor [Resolved] private SkinManager skins { get; set; } - public SkinEditor(Drawable target) + public SkinEditor(Drawable targetScreen) { - this.target = target; + this.targetScreen = targetScreen; RelativeSizeAxes = Axes.Both; } @@ -52,18 +53,20 @@ namespace osu.Game.Skinning.Editor Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X }, - new SkinBlueprintContainer(target), + new SkinBlueprintContainer(targetScreen), new SkinComponentToolbox(600) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RequestPlacement = placeComponent }, - new TriangleButton + new FillFlowContainer { - Text = "Save Changes", - Width = 140, - Height = 50, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Spacing = new Vector2(5), Padding = new MarginPadding { Top = 10, @@ -74,7 +77,21 @@ namespace osu.Game.Skinning.Editor Right = 10, Bottom = 10, }, - Action = save, + Children = new Drawable[] + { + new TriangleButton + { + Text = "Save Changes", + Width = 140, + Action = save, + }, + new DangerousTriangleButton + { + Text = "Revert to default", + Width = 140, + Action = revert, + }, + } }, } }; @@ -89,11 +106,14 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - var instance = (Drawable)Activator.CreateInstance(type); + Drawable instance = (Drawable)Activator.CreateInstance(type); - var targetContainer = target.ChildrenOfType().FirstOrDefault(); + getTarget(SkinnableTarget.MainHUDComponents)?.Add(instance); + } - (targetContainer as Container)?.Add(instance); + private ISkinnableTarget getTarget(SkinnableTarget target) + { + return targetScreen.ChildrenOfType().FirstOrDefault(c => c.Target == target); } protected override void LoadComplete() @@ -102,6 +122,26 @@ namespace osu.Game.Skinning.Editor Show(); } + private void revert() + { + var currentSkin = skins.CurrentSkin.Value; + + var legacySkin = currentSkin as LegacySkin; + + if (legacySkin == null) + return; + + SkinnableElementTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); + + foreach (var t in targetContainers) + { + legacySkin.ResetDrawableTarget(t); + + // add back default components + getTarget(t.Target).Reload(); + } + } + private void save() { var currentSkin = skins.CurrentSkin.Value; @@ -111,7 +151,7 @@ namespace osu.Game.Skinning.Editor if (legacySkin == null) return; - SkinnableElementTargetContainer[] targetContainers = target.ChildrenOfType().ToArray(); + SkinnableElementTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); foreach (var t in targetContainers) legacySkin.UpdateDrawableTarget(t); From bf65547eec7419273bd106f30d4842dd19ea86fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 12:04:25 +0900 Subject: [PATCH 493/708] Allow some serialised components to not be mutable by the user --- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 4 ++-- osu.Game/Skinning/IImmutableSkinnableComponent.cs | 15 +++++++++++++++ osu.Game/Skinning/ISkinnableComponent.cs | 4 +--- osu.Game/Skinning/Skin.cs | 5 +---- 4 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Skinning/IImmutableSkinnableComponent.cs diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index abeb96f647..988d73fed6 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { /// - /// Serialised information governing custom changes to an . + /// Serialised information governing custom changes to an . /// [Serializable] public class SkinnableInfo : IJsonSerializable @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play.HUD if (component is Container container) { - foreach (var child in container.Children.OfType().OfType()) + foreach (var child in container.Children.OfType().OfType()) Children.Add(child.CreateSerialisedInformation()); } } diff --git a/osu.Game/Skinning/IImmutableSkinnableComponent.cs b/osu.Game/Skinning/IImmutableSkinnableComponent.cs new file mode 100644 index 0000000000..d1777512af --- /dev/null +++ b/osu.Game/Skinning/IImmutableSkinnableComponent.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; + +namespace osu.Game.Skinning +{ + /// + /// Denotes a drawable component which should be serialised as part of a skin. + /// Use for components which should be mutable by the user / editor. + /// + public interface ISkinSerialisable : IDrawable + { + } +} diff --git a/osu.Game/Skinning/ISkinnableComponent.cs b/osu.Game/Skinning/ISkinnableComponent.cs index f6b0a182b4..e44c7be13d 100644 --- a/osu.Game/Skinning/ISkinnableComponent.cs +++ b/osu.Game/Skinning/ISkinnableComponent.cs @@ -1,14 +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.Graphics; - namespace osu.Game.Skinning { /// /// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications. /// - public interface ISkinnableComponent : IDrawable + public interface ISkinnableComponent : ISkinSerialisable { } } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 34571ec688..cbbad4a607 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -22,8 +22,6 @@ namespace osu.Game.Skinning { public readonly SkinInfo SkinInfo; - private readonly IStorageResourceProvider resources; - public SkinConfiguration Configuration { get; protected set; } public IDictionary DrawableComponentInfo => drawableComponentInfo; @@ -43,7 +41,6 @@ namespace osu.Game.Skinning SkinInfo = skin; // may be null for default skin. - this.resources = resources; // we may want to move this to some kind of async operation in the future. foreach (SkinnableTarget skinnableTarget in Enum.GetValues(typeof(SkinnableTarget))) @@ -124,7 +121,7 @@ namespace osu.Game.Skinning #endregion } - public class SkinnableTargetWrapper : Container, ISkinnableComponent + public class SkinnableTargetWrapper : Container, ISkinSerialisable { public SkinnableTargetWrapper() { From 2396ba42a6ec6c6e9f3e4b8d89513fec123706e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 12:21:31 +0900 Subject: [PATCH 494/708] Change `HealthDisplay` to be a `CompositeDrawable` --- osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs | 6 ++++-- osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs | 2 +- osu.Game/Screens/Play/HUD/FailingLayer.cs | 2 +- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index fb4c9d713a..c335f7c99e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -50,9 +52,9 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddStep("set health to 0.10", () => layer.Current.Value = 0.1); - AddUntilStep("layer fade is visible", () => layer.Child.Alpha > 0.1f); + AddUntilStep("layer fade is visible", () => layer.ChildrenOfType().First().Alpha > 0.1f); AddStep("set health to 1", () => layer.Current.Value = 1f); - AddUntilStep("layer fade is invisible", () => !layer.Child.IsPresent); + AddUntilStep("layer fade is invisible", () => !layer.ChildrenOfType().First().IsPresent); } [Test] diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index 37bd289aee..241777244b 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play.HUD RelativeSizeAxes = Axes.X; Margin = new MarginPadding { Top = 20 }; - Children = new Drawable[] + InternalChildren = new Drawable[] { new Box { diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index e071337f34..424ee55766 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play.HUD public FailingLayer() { RelativeSizeAxes = Axes.Both; - Children = new Drawable[] + InternalChildren = new Drawable[] { boxes = new Container { diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index e18a28eded..5a2c764a1a 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play.HUD /// A container for components displaying the current player health. /// Gets bound automatically to the when inserted to hierarchy. /// - public abstract class HealthDisplay : Container + public abstract class HealthDisplay : CompositeDrawable { private Bindable showHealthbar; From 4c4d75e6f9c341548b310a767cc15d6839559905 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 12:42:32 +0900 Subject: [PATCH 495/708] Remove `AccuracyCounter` sizing dependency in `HUDOverlay` --- .../HUD/SkinnableElementTargetContainer.cs | 2 ++ osu.Game/Screens/Play/HUDOverlay.cs | 34 ++++++++++++++----- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs index 79a0db1751..051ab9ad3c 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Play.HUD Target = target; } + public IReadOnlyList Children => content?.Children; + public void Reload() { content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetWrapper; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 30b735d28a..033a280da2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,7 +36,6 @@ namespace osu.Game.Screens.Play public readonly KeyCounterDisplay KeyCounter; public readonly SkinnableScoreCounter ScoreCounter; - public readonly SkinnableAccuracyCounter AccuracyCounter; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; @@ -68,6 +68,8 @@ namespace osu.Game.Screens.Play private bool holdingForHUD; + private readonly SkinnableElementTargetContainer mainComponents; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter, topRightElements }; public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -95,7 +97,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new SkinnableElementTargetContainer(SkinnableTarget.MainHUDComponents) + mainComponents = new SkinnableElementTargetContainer(SkinnableTarget.MainHUDComponents) { RelativeSizeAxes = Axes.Both, }, @@ -107,7 +109,6 @@ namespace osu.Game.Screens.Play { // remaining cross-dependencies need tidying. // kept to ensure non-null, but hidden for testing. - AccuracyCounter = new SkinnableAccuracyCounter(), ScoreCounter = new SkinnableScoreCounter(), } }, @@ -216,12 +217,29 @@ namespace osu.Game.Screens.Play { base.Update(); - // HACK: for now align with the accuracy counter. - // this is done for the sake of hacky legacy skins which extend the health bar to take up the full screen area. - // it only works with the default skin due to padding offsetting it *just enough* to coexist. - topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(AccuracyCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; + Vector2 lowestScreenSpace = Vector2.Zero; - bottomRightElements.Y = -Progress.Height; + // TODO: may be null during skin switching. not sure if there's a better way of exposing these children. + if (mainComponents.Children != null) + { + foreach (var element in mainComponents.Children) + { + // for now align top-right components with the bottom-edge of the lowest top-anchored hud element. + if (!element.Anchor.HasFlagFast(Anchor.TopRight)) + continue; + + // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. + if (element is HealthDisplay) + continue; + + var bottomRight = element.ScreenSpaceDrawQuad.BottomRight; + if (bottomRight.Y > lowestScreenSpace.Y) + lowestScreenSpace = bottomRight; + } + + topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestScreenSpace).Y; + bottomRightElements.Y = -Progress.Height; + } } private void updateVisibility() From 117d6d731d1a0f5de4648888cfd91f180b1cac2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 13:12:24 +0900 Subject: [PATCH 496/708] Move cross-component layout dependencies for legacy skin to `LegacySkin` --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 11 ------ osu.Game/Skinning/LegacySkin.cs | 15 +++++++- osu.Game/Skinning/Skin.cs | 9 ----- osu.Game/Skinning/SkinnableTargetWrapper.cs | 41 +++++++++++++++++++++ 4 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 osu.Game/Skinning/SkinnableTargetWrapper.cs diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index e2fe01efe4..3eea5d22d5 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -32,16 +32,5 @@ namespace osu.Game.Skinning Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }; - - protected override void Update() - { - base.Update(); - - if (hud?.ScoreCounter?.Drawable is LegacyScoreCounter score) - { - // for now align with the score counter. eventually this will be user customisable. - Y = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; - } - } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3ffd2245c2..85cfab8297 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -327,11 +327,20 @@ namespace osu.Game.Skinning switch (component) { case SkinnableTargetComponent target: - switch (target.Target) { case SkinnableTarget.MainHUDComponents: - return new SkinnableTargetWrapper + + var skinnableTargetWrapper = new SkinnableTargetWrapper(container => + { + var score = container.OfType().FirstOrDefault(); + var accuracy = container.OfType().FirstOrDefault(); + + if (score != null && accuracy != null) + { + accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; + } + }) { Children = new[] { @@ -342,6 +351,8 @@ namespace osu.Game.Skinning GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(), } }; + + return skinnableTargetWrapper; } return null; diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index cbbad4a607..10481e4efd 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -9,7 +9,6 @@ using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; @@ -120,12 +119,4 @@ namespace osu.Game.Skinning #endregion } - - public class SkinnableTargetWrapper : Container, ISkinSerialisable - { - public SkinnableTargetWrapper() - { - RelativeSizeAxes = Axes.Both; - } - } } diff --git a/osu.Game/Skinning/SkinnableTargetWrapper.cs b/osu.Game/Skinning/SkinnableTargetWrapper.cs new file mode 100644 index 0000000000..a0df610488 --- /dev/null +++ b/osu.Game/Skinning/SkinnableTargetWrapper.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Skinning +{ + /// + /// A container which is serialised and can encapsulate multiple skinnable elements into a single return type. + /// Will also optionally apply default cross-element layout dependencies when initialised from a non-deserialised source. + /// + public class SkinnableTargetWrapper : Container, ISkinSerialisable + { + private readonly Action applyDefaults; + + /// + /// Construct a wrapper with defaults that should be applied once. + /// + /// A function with default to apply after the initial layout (ie. consuming autosize) + public SkinnableTargetWrapper(Action applyDefaults) + : this() + { + this.applyDefaults = applyDefaults; + } + + public SkinnableTargetWrapper() + { + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // schedule is required to allow children to run their LoadComplete and take on their correct sizes. + Schedule(() => applyDefaults?.Invoke(this)); + } + } +} From f53ce951dc1ee6594d5a6cc4e975fa9e9a15dee6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 13:13:40 +0900 Subject: [PATCH 497/708] Remove `DefaultScoreCounter` animation for the time being May add this back in the future, but for now it's causing issues as it operates on `this`. The default skin may be changing quite a bit in the near future, so we can decide what to do about animation at that point in time. --- osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs index 8e37797446..bd18050dfb 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -24,12 +24,6 @@ namespace osu.Game.Screens.Play.HUD private void load(OsuColour colours) { Colour = colours.BlueLighter; - - // todo: check if default once health display is skinnable - hud?.ShowHealthbar.BindValueChanged(healthBar => - { - this.MoveToY(healthBar.NewValue ? 30 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING); - }, true); } } } From c94df672e5f30c4d87738ca37a019685773c4df2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 13:39:32 +0900 Subject: [PATCH 498/708] Also serialise `Origin` out --- osu.Game/Extensions/DrawableExtensions.cs | 1 + osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 6837a802f2..0ec96a876e 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -55,6 +55,7 @@ namespace osu.Game.Extensions component.Rotation = info.Rotation; component.Scale = info.Scale; component.Anchor = info.Anchor; + component.Origin = info.Origin; if (component is Container container) { diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 988d73fed6..2de3f1b729 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Play.HUD public Anchor Anchor { get; set; } + public Anchor Origin { get; set; } + public List Children { get; } = new List(); public SkinnableInfo() @@ -43,6 +45,7 @@ namespace osu.Game.Screens.Play.HUD Rotation = component.Rotation; Scale = component.Scale; Anchor = component.Anchor; + Origin = component.Origin; if (component is Container container) { From 12684de66eb76365a3f10d4bee629eb9f0ada1d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 14:09:56 +0900 Subject: [PATCH 499/708] Add ability to adjust origin in skin editor --- .../Skinning/Editor/SkinSelectionHandler.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index ad783a9c0e..cf5ece03e9 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -70,13 +70,18 @@ namespace osu.Game.Skinning.Editor { yield return new OsuMenuItem("Anchor") { - Items = createAnchorItems().ToArray() + Items = createAnchorItems(d => d.Anchor, applyAnchor).ToArray() + }; + + yield return new OsuMenuItem("Origin") + { + Items = createAnchorItems(d => d.Origin, applyOrigin).ToArray() }; foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - IEnumerable createAnchorItems() + IEnumerable createAnchorItems(Func checkFunction, Action applyFunction) { var displayableAnchors = new[] { @@ -93,14 +98,20 @@ namespace osu.Game.Skinning.Editor return displayableAnchors.Select(a => { - return new AnchorMenuItem(a, selection, _ => applyAnchor(a)) + return new AnchorMenuItem(a, selection, _ => applyFunction(a)) { - State = { Value = GetStateFromSelection(selection, c => ((Drawable)c.Item).Anchor == a) } + State = { Value = GetStateFromSelection(selection, c => checkFunction((Drawable)c.Item) == a) } }; }); } } + private void applyOrigin(Anchor anchor) + { + foreach (var item in SelectedItems) + ((Drawable)item).Origin = anchor; + } + private void applyAnchor(Anchor anchor) { foreach (var item in SelectedItems) From 944f09ec98e54f296ee6fe04a0b183ab31bd4550 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 14:12:28 +0900 Subject: [PATCH 500/708] Move default skin cross-component dependencies out to default specifications --- .../Play/HUD/DefaultAccuracyCounter.cs | 22 ------- .../Screens/Play/HUD/DefaultComboCounter.cs | 14 ---- osu.Game/Screens/Play/HUDOverlay.cs | 12 ---- osu.Game/Skinning/DefaultSkin.cs | 64 +++++++++++++++++++ 4 files changed, 64 insertions(+), 48 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index e4c865803d..d8dff89b29 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -2,23 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Screens.Play.HUD { public class DefaultAccuracyCounter : GameplayAccuracyCounter, ISkinnableComponent { - private readonly Vector2 offset = new Vector2(-20, 5); - - public DefaultAccuracyCounter() - { - Origin = Anchor.TopRight; - Anchor = Anchor.TopRight; - } - [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } @@ -27,17 +17,5 @@ namespace osu.Game.Screens.Play.HUD { Colour = colours.BlueLighter; } - - protected override void Update() - { - base.Update(); - - if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score) - { - // for now align with the score counter. eventually this will be user customisable. - Anchor = Anchor.TopLeft; - Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.TopLeft) + offset; - } - } } } diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 375ff293aa..13bd045fc0 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -9,14 +9,11 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Screens.Play.HUD { public class DefaultComboCounter : RollingCounter, ISkinnableComponent { - private readonly Vector2 offset = new Vector2(20, 5); - [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } @@ -32,17 +29,6 @@ namespace osu.Game.Screens.Play.HUD Current.BindTo(scoreProcessor.Combo); } - protected override void Update() - { - base.Update(); - - if (hud?.ScoreCounter.Drawable is DefaultScoreCounter score) - { - // for now align with the score counter. eventually this will be user customisable. - Position = Parent.ToLocalSpace(score.ScreenSpaceDrawQuad.TopRight) + offset; - } - } - protected override string FormatCount(int count) { return $@"{count}x"; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 033a280da2..88176e7ea2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,7 +35,6 @@ namespace osu.Game.Screens.Play public float TopScoringElementsHeight { get; private set; } public readonly KeyCounterDisplay KeyCounter; - public readonly SkinnableScoreCounter ScoreCounter; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; @@ -102,17 +101,6 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, }, new Container - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Children = new Drawable[] - { - // remaining cross-dependencies need tidying. - // kept to ensure non-null, but hidden for testing. - ScoreCounter = new SkinnableScoreCounter(), - } - }, - new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index bfb5b8ef56..ef3dffe59c 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -2,11 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.Extensions; +using osu.Game.Screens.Play.HUD; +using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -23,6 +28,65 @@ namespace osu.Game.Skinning public override ISample GetSample(ISampleInfo sampleInfo) => null; + public override Drawable GetDrawableComponent(ISkinComponent component) + { + switch (component) + { + case SkinnableTargetComponent target: + switch (target.Target) + { + case SkinnableTarget.MainHUDComponents: + var skinnableTargetWrapper = new SkinnableTargetWrapper(container => + { + var score = container.OfType().FirstOrDefault(); + var accuracy = container.OfType().FirstOrDefault(); + var combo = container.OfType().FirstOrDefault(); + + if (score != null) + { + score.Anchor = Anchor.TopCentre; + score.Origin = Anchor.TopCentre; + + // elements default to beneath the health bar + const float vertical_offset = 30; + + const float horizontal_padding = 20; + + score.Position = new Vector2(0, vertical_offset); + + if (accuracy != null) + { + accuracy.Position = new Vector2(-accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding, vertical_offset + 5); + accuracy.Origin = Anchor.TopRight; + accuracy.Anchor = Anchor.TopCentre; + } + + if (combo != null) + { + combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5); + combo.Anchor = Anchor.TopCentre; + } + } + }) + { + Children = new Drawable[] + { + new DefaultComboCounter(), + new DefaultScoreCounter(), + new DefaultAccuracyCounter(), + new DefaultHealthDisplay(), + } + }; + + return skinnableTargetWrapper; + } + + return null; + } + + return base.GetDrawableComponent(component); + } + public override IBindable GetConfig(TLookup lookup) { switch (lookup) From 03d5f10744b91aa189d5bf1fe5a153a2117830b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 14:22:19 +0900 Subject: [PATCH 501/708] Fix default health bar not being considered for top-right flow layout --- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 88176e7ea2..c4a8860bb6 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -213,11 +213,11 @@ namespace osu.Game.Screens.Play foreach (var element in mainComponents.Children) { // for now align top-right components with the bottom-edge of the lowest top-anchored hud element. - if (!element.Anchor.HasFlagFast(Anchor.TopRight)) + if (!element.Anchor.HasFlagFast(Anchor.TopRight) && !element.RelativeSizeAxes.HasFlagFast(Axes.X)) continue; // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. - if (element is HealthDisplay) + if (element is LegacyHealthDisplay) continue; var bottomRight = element.ScreenSpaceDrawQuad.BottomRight; From d5fe4f0f72e404f9527e2ccfe444dcd6645de0fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 14:34:49 +0900 Subject: [PATCH 502/708] Remove unused skin resolution in `LegacyScoreCounter` --- osu.Game/Skinning/LegacyScoreCounter.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 0696d2bedd..8aa48da453 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; @@ -14,9 +13,6 @@ namespace osu.Game.Skinning protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; - [Resolved] - private ISkinSource skin { get; set; } - public LegacyScoreCounter() : base(6) { From 0cf3efa16b9c20dccd26722e599ffa75bc5b9b72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 16:56:23 +0900 Subject: [PATCH 503/708] Remove customisation support for `SongProgressDisplay` --- osu.Game/Screens/Play/SongProgress.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index d85f3538e4..4a0787bfae 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -14,7 +14,6 @@ using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; -using osu.Game.Skinning; namespace osu.Game.Screens.Play { @@ -181,12 +180,12 @@ namespace osu.Game.Screens.Play info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In); } - public class SongProgressDisplay : Container, ISkinnableComponent + public class SongProgressDisplay : Container { public SongProgressDisplay() { // TODO: move actual implementation into this. - // exists for skin customisation purposes. + // exists for skin customisation purposes (interface should be added to this container). Masking = true; RelativeSizeAxes = Axes.Both; From f6f4b90d2bd71680a470d92c0aef5a98494e8580 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 16:56:55 +0900 Subject: [PATCH 504/708] Add customisation support for `LegacyHealthDisplay` --- osu.Game/Skinning/LegacyHealthDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index 717ca62da7..dfb6ca1a64 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 : HealthDisplay + public class LegacyHealthDisplay : HealthDisplay, ISkinnableComponent { private const double epic_cutoff = 0.5; From a67cead0b3adfce14608060d51131145ff653c81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 17:00:24 +0900 Subject: [PATCH 505/708] Add `SkinInfo.InstantiationInfo` to allow creating different skin types --- .../TestSceneHitCircleArea.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- ...60743_AddSkinInstantiationInfo.Designer.cs | 508 ++++++++++++++++++ ...20210511060743_AddSkinInstantiationInfo.cs | 22 + .../Migrations/OsuDbContextModelSnapshot.cs | 6 +- osu.Game/Skinning/DefaultLegacySkin.cs | 10 +- osu.Game/Skinning/DefaultSkin.cs | 10 +- osu.Game/Skinning/SkinInfo.cs | 36 +- osu.Game/Skinning/SkinManager.cs | 22 +- 9 files changed, 597 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs index 0649989dc0..1fdcd73dde 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = new SkinProvidingContainer(new DefaultSkin()) + Child = new SkinProvidingContainer(new DefaultSkin(null)) { RelativeSizeAxes = Axes.Both, Child = drawableHitCircle = new DrawableHitCircle(hitCircle) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index e0eeaf6db0..3576b149bf 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps public bool SkinLoaded => skin.IsResultAvailable; public ISkin Skin => skin.Value; - protected virtual ISkin GetSkin() => new DefaultSkin(); + protected virtual ISkin GetSkin() => new DefaultSkin(null); private readonly RecyclableLazy skin; public abstract Stream GetStream(string storagePath); diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs new file mode 100644 index 0000000000..b808c648da --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210511060743_AddSkinInstantiationInfo")] + partial class AddSkinInstantiationInfo + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs new file mode 100644 index 0000000000..1d5b0769a4 --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInstantiationInfo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "InstantiationInfo", + table: "SkinInfo", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "InstantiationInfo", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index ec4461ca56..d4bde50b60 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -141,8 +141,6 @@ namespace osu.Game.Migrations b.Property("TitleUnicode"); - b.Property("VideoFile"); - b.HasKey("ID"); b.ToTable("BeatmapMetadata"); @@ -352,7 +350,7 @@ namespace osu.Game.Migrations b.Property("TotalScore"); - b.Property("UserID") + b.Property("UserID") .HasColumnName("UserID"); b.Property("UserString") @@ -402,6 +400,8 @@ namespace osu.Game.Migrations b.Property("Hash"); + b.Property("InstantiationInfo"); + b.Property("Name"); b.HasKey("ID"); diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 4027cc650d..564be8630e 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -10,7 +10,12 @@ namespace osu.Game.Skinning public class DefaultLegacySkin : LegacySkin { public DefaultLegacySkin(IResourceStore storage, IStorageResourceProvider resources) - : base(Info, storage, resources, string.Empty) + : this(Info, storage, resources) + { + } + + public DefaultLegacySkin(SkinInfo skin, IResourceStore storage, IStorageResourceProvider resources) + : base(skin, storage, resources, string.Empty) { Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); Configuration.AddComboColours( @@ -27,7 +32,8 @@ namespace osu.Game.Skinning { ID = SkinInfo.CLASSIC_SKIN, // this is temporary until database storage is decided upon. Name = "osu!classic", - Creator = "team osu!" + Creator = "team osu!", + InstantiationInfo = typeof(DefaultLegacySkin).AssemblyQualifiedName, }; } } diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index ef3dffe59c..1c063b6ef2 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Extensions; +using osu.Game.IO; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Graphics; @@ -18,8 +19,13 @@ namespace osu.Game.Skinning { public class DefaultSkin : Skin { - public DefaultSkin() - : base(SkinInfo.Default, null) + public DefaultSkin(IStorageResourceProvider resources) + : this(SkinInfo.Default, resources) + { + } + + public DefaultSkin(SkinInfo skin, IStorageResourceProvider resources) + : base(skin, resources) { Configuration = new DefaultSkinConfiguration(); } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index aaccbefb3d..2e29808cb4 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Linq; +using osu.Framework.IO.Stores; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.IO; namespace osu.Game.Skinning { @@ -22,7 +25,35 @@ namespace osu.Game.Skinning public string Creator { get; set; } - public List Files { get; set; } + private string instantiationInfo; + + public string InstantiationInfo + { + get => instantiationInfo; + set => instantiationInfo = abbreviateInstantiationInfo(value); + } + + private string abbreviateInstantiationInfo(string value) + { + // exclude version onwards, matching only on namespace and type. + // this is mainly to allow for new versions of already loaded rulesets to "upgrade" from old. + return string.Join(',', value.Split(',').Take(2)); + } + + public virtual Skin CreateInstance(IResourceStore legacyDefaultResources, IStorageResourceProvider resources) + { + var type = string.IsNullOrEmpty(InstantiationInfo) + // handle the case of skins imported before InstantiationInfo was added. + ? typeof(LegacySkin) + : Type.GetType(InstantiationInfo); + + if (type == typeof(DefaultLegacySkin)) + return (Skin)Activator.CreateInstance(type, this, legacyDefaultResources, resources); + + return (Skin)Activator.CreateInstance(type, this, resources); + } + + public List Files { get; set; } = new List(); public List Settings { get; set; } @@ -32,7 +63,8 @@ namespace osu.Game.Skinning { ID = DEFAULT_SKIN, Name = "osu!lazer", - Creator = "team osu!" + Creator = "team osu!", + InstantiationInfo = typeof(DefaultSkin).AssemblyQualifiedName, }; public bool Equals(SkinInfo other) => other != null && ID == other.ID; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index f9e22c9c89..2ea236e44f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -39,7 +39,7 @@ namespace osu.Game.Skinning private readonly IResourceStore legacyDefaultResources; - public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin()); + public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin(null)); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; public override IEnumerable HandledExtensions => new[] { ".osk" }; @@ -108,7 +108,7 @@ namespace osu.Game.Skinning { // we need to populate early to create a hash based off skin.ini contents if (item.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) - populateMetadata(item); + populateMetadata(item, GetSkin(item)); if (item.Creator != null && item.Creator != unknown_creator_string) { @@ -125,18 +125,20 @@ namespace osu.Game.Skinning { await base.Populate(model, archive, cancellationToken).ConfigureAwait(false); + var instance = GetSkin(model); + + model.InstantiationInfo ??= instance.GetType().AssemblyQualifiedName; + if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) - populateMetadata(model); + populateMetadata(model, instance); } - private void populateMetadata(SkinInfo item) + private void populateMetadata(SkinInfo item, Skin instance) { - Skin reference = GetSkin(item); - - if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) + if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name)) { - item.Name = reference.Configuration.SkinInfo.Name; - item.Creator = reference.Configuration.SkinInfo.Creator; + item.Name = instance.Configuration.SkinInfo.Name; + item.Creator = instance.Configuration.SkinInfo.Creator; } else { @@ -150,7 +152,7 @@ namespace osu.Game.Skinning /// /// The skin to lookup. /// A instance correlating to the provided . - public Skin GetSkin(SkinInfo skinInfo) + public Skin GetSkin(SkinInfo skinInfo) => skinInfo.CreateInstance(legacyDefaultResources, this); { if (skinInfo == SkinInfo.Default) return new DefaultSkin(); From a7e83aacfb2b01201f7f8d87f584fbc871c30b3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 17:00:56 +0900 Subject: [PATCH 506/708] Ensure default skins are copied before modifying --- osu.Game/Skinning/Editor/SkinEditor.cs | 30 ++++++++++++-------------- osu.Game/Skinning/SkinManager.cs | 26 ++++++++++++++-------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 6b7d289284..90b003153b 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -4,6 +4,7 @@ using System; 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; @@ -30,6 +31,8 @@ namespace osu.Game.Skinning.Editor [Resolved] private SkinManager skins { get; set; } + private Bindable currentSkin; + public SkinEditor(Drawable targetScreen) { this.targetScreen = targetScreen; @@ -119,23 +122,25 @@ namespace osu.Game.Skinning.Editor protected override void LoadComplete() { base.LoadComplete(); + Show(); + + // as long as the skin editor is loaded, let's make sure we can modify the current skin. + currentSkin = skins.CurrentSkin.GetBoundCopy(); + + // schedule ensures this only happens when the skin editor is visible. + // also avoid some weird endless recursion / bindable feedback loop (something to do with tracking skins across three different bindable types). + // probably something which will be factored out in a future database refactor so not too concerning for now. + currentSkin.BindValueChanged(skin => Scheduler.AddOnce(skins.EnsureMutableSkin), true); } private void revert() { - var currentSkin = skins.CurrentSkin.Value; - - var legacySkin = currentSkin as LegacySkin; - - if (legacySkin == null) - return; - SkinnableElementTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); foreach (var t in targetContainers) { - legacySkin.ResetDrawableTarget(t); + currentSkin.Value.ResetDrawableTarget(t); // add back default components getTarget(t.Target).Reload(); @@ -144,17 +149,10 @@ namespace osu.Game.Skinning.Editor private void save() { - var currentSkin = skins.CurrentSkin.Value; - - var legacySkin = currentSkin as LegacySkin; - - if (legacySkin == null) - return; - SkinnableElementTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); foreach (var t in targetContainers) - legacySkin.UpdateDrawableTarget(t); + currentSkin.Value.UpdateDrawableTarget(t); skins.Save(skins.CurrentSkin.Value); } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 2ea236e44f..63d7e4f86f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -153,22 +153,30 @@ namespace osu.Game.Skinning /// The skin to lookup. /// A instance correlating to the provided . public Skin GetSkin(SkinInfo skinInfo) => skinInfo.CreateInstance(legacyDefaultResources, this); + + /// + /// Ensure that the current skin is in a state it can accept user modifications. + /// This will create a copy of any internal skin and being tracking in the database if not already. + /// + public void EnsureMutableSkin() { - if (skinInfo == SkinInfo.Default) - return new DefaultSkin(); + if (CurrentSkinInfo.Value.ID >= 1) return; - if (skinInfo == DefaultLegacySkin.Info) - return new DefaultLegacySkin(legacyDefaultResources, this); + var skin = CurrentSkin.Value; - return new LegacySkin(skinInfo, this); + // if the user is attempting to save one of the default skin implementations, create a copy first. + CurrentSkinInfo.Value = Import(new SkinInfo + { + Name = skin.SkinInfo.Name + " (modified)", + Creator = skin.SkinInfo.Creator, + InstantiationInfo = skin.SkinInfo.InstantiationInfo, + }).Result; } public void Save(Skin skin) { - // some skins don't support saving just yet. - // eventually we will want to create a copy of the skin to allow for customisation. - if (skin.SkinInfo.Files == null) - return; + if (skin.SkinInfo.ID <= 0) + throw new InvalidOperationException($"Attempting to save a skin which is not yet tracked. Call {nameof(EnsureMutableSkin)} first."); foreach (var drawableInfo in skin.DrawableComponentInfo) { From 61ea3f2e6410980341803eb2c4548e917229ee3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 17:08:32 +0900 Subject: [PATCH 507/708] Remove unnecessary test step creating needless skins --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index b0edc0dd68..73da76df78 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -22,13 +22,6 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUpSteps] public override void SetUpSteps() { - AddStep("set empty legacy skin", () => - { - var imported = skinManager.Import(new SkinInfo { Name = "test skin" }).Result; - - skinManager.CurrentSkinInfo.Value = imported; - }); - base.SetUpSteps(); AddStep("reload skin editor", () => From a88a8b7d8d504cf981bbcd65c1d81ba9a69a6320 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 17:48:08 +0900 Subject: [PATCH 508/708] Use `ISkinnableComponent` wherever possible (and expose as `BindableList`) --- .../HUD/SkinnableElementTargetContainer.cs | 32 +++++++++++++++---- osu.Game/Screens/Play/HUDOverlay.cs | 32 +++++++++---------- osu.Game/Skinning/ISkinnableTarget.cs | 4 +-- osu.Game/Skinning/SkinnableTargetWrapper.cs | 2 +- 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs index 051ab9ad3c..b2f6d32927 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.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.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Extensions; using osu.Game.Skinning; @@ -15,30 +17,46 @@ namespace osu.Game.Screens.Play.HUD public SkinnableTarget Target { get; } + public IBindableList Components => components; + + private readonly BindableList components = new BindableList(); + public SkinnableElementTargetContainer(SkinnableTarget target) { Target = target; } - public IReadOnlyList Children => content?.Children; - public void Reload() { + ClearInternal(); + components.Clear(); + content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetWrapper; - ClearInternal(); - if (content != null) - LoadComponentAsync(content, AddInternal); + { + LoadComponentAsync(content, wrapper => + { + AddInternal(wrapper); + components.AddRange(wrapper.Children.OfType()); + }); + } } - public void Add(Drawable drawable) + public void Add(ISkinnableComponent component) { + if (content == null) + throw new NotSupportedException("Attempting to add a new component to a target container which is not supported by the current skin."); + + if (!(component is Drawable drawable)) + throw new ArgumentException("Provided argument must be of type {nameof(ISkinnableComponent)}.", nameof(drawable)); + content.Add(drawable); + components.Add(component); } public IEnumerable CreateSerialisedChildren() => - content.Select(d => d.CreateSerialisedInformation()); + components.Select(d => ((Drawable)d).CreateSerialisedInformation()); protected override void SkinChanged(ISkinSource skin, bool allowFallback) { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index c4a8860bb6..887346b5df 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; @@ -207,27 +208,24 @@ namespace osu.Game.Screens.Play Vector2 lowestScreenSpace = Vector2.Zero; - // TODO: may be null during skin switching. not sure if there's a better way of exposing these children. - if (mainComponents.Children != null) + // LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. + foreach (var element in mainComponents.Components.Cast()) { - foreach (var element in mainComponents.Children) - { - // for now align top-right components with the bottom-edge of the lowest top-anchored hud element. - if (!element.Anchor.HasFlagFast(Anchor.TopRight) && !element.RelativeSizeAxes.HasFlagFast(Axes.X)) - continue; + // for now align top-right components with the bottom-edge of the lowest top-anchored hud element. + if (!element.Anchor.HasFlagFast(Anchor.TopRight) && !element.RelativeSizeAxes.HasFlagFast(Axes.X)) + continue; - // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. - if (element is LegacyHealthDisplay) - continue; + // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. + if (element is LegacyHealthDisplay) + continue; - var bottomRight = element.ScreenSpaceDrawQuad.BottomRight; - if (bottomRight.Y > lowestScreenSpace.Y) - lowestScreenSpace = bottomRight; - } - - topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestScreenSpace).Y; - bottomRightElements.Y = -Progress.Height; + var bottomRight = element.ScreenSpaceDrawQuad.BottomRight; + if (bottomRight.Y > lowestScreenSpace.Y) + lowestScreenSpace = bottomRight; } + + topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestScreenSpace).Y; + bottomRightElements.Y = -Progress.Height; } private void updateVisibility() diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 2379fd4247..4d97b42e8e 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics; - namespace osu.Game.Skinning { /// @@ -20,6 +18,6 @@ namespace osu.Game.Skinning /// /// Add the provided item to this target. /// - public void Add(Drawable drawable); + public void Add(ISkinnableComponent drawable); } } diff --git a/osu.Game/Skinning/SkinnableTargetWrapper.cs b/osu.Game/Skinning/SkinnableTargetWrapper.cs index a0df610488..0d16775f51 100644 --- a/osu.Game/Skinning/SkinnableTargetWrapper.cs +++ b/osu.Game/Skinning/SkinnableTargetWrapper.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Skinning { /// - /// A container which is serialised and can encapsulate multiple skinnable elements into a single return type. + /// A container which is serialised and can encapsulate multiple skinnable elements into a single return type (for consumption via . /// Will also optionally apply default cross-element layout dependencies when initialised from a non-deserialised source. /// public class SkinnableTargetWrapper : Container, ISkinSerialisable From a4e052961770c3eb98408393ad198287ebcb0766 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 17:49:00 +0900 Subject: [PATCH 509/708] Replace polling logic with direct bindable reactions --- .../Compose/Components/BlueprintContainer.cs | 23 +++++++++ .../Components/EditorBlueprintContainer.cs | 22 +-------- .../Skinning/Editor/SkinBlueprintContainer.cs | 49 ++++++++++++++++--- osu.Game/Skinning/Editor/SkinEditor.cs | 15 +++++- 4 files changed, 78 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 361e98e0dd..edd1acbd6c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -3,9 +3,11 @@ 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; @@ -24,6 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Includes selection and manipulation support via a . /// public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler + where T : class { protected DragBox DragBox { get; private set; } @@ -39,6 +42,8 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } + protected readonly BindableList SelectedItems = new BindableList(); + protected BlueprintContainer() { RelativeSizeAxes = Axes.Both; @@ -47,6 +52,24 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { + SelectedItems.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; + } + }; + SelectionHandler = CreateSelectionHandler(); SelectionHandler.DeselectAll = deselectAll; diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs index db322faf65..31a191c80c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorBlueprintContainer.cs @@ -2,10 +2,8 @@ // 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; @@ -24,8 +22,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected readonly HitObjectComposer Composer; - private readonly BindableList selectedHitObjects = new BindableList(); - protected EditorBlueprintContainer(HitObjectComposer composer) { Composer = composer; @@ -34,23 +30,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - 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; - } - }; + SelectedItems.BindTo(Beatmap.SelectedHitObjects); } protected override void LoadComplete() diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 45e541abf0..bc1ea02090 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -1,11 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; 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 { @@ -18,23 +23,51 @@ namespace osu.Game.Skinning.Editor this.target = target; } + [BackgroundDependencyLoader(true)] + private void load(SkinEditor editor) + { + SelectedItems.BindTo(editor.SelectedComponents); + } + + private readonly List> targetComponents = new List>(); + protected override void LoadComplete() { base.LoadComplete(); - checkForComponents(); + // track each target container on the current screen. + foreach (var targetContainer in target.ChildrenOfType()) + { + var bindableList = new BindableList { BindTarget = targetContainer.Components }; + bindableList.BindCollectionChanged(componentsChanged, true); + + targetComponents.Add(bindableList); + } } - private void checkForComponents() + private void componentsChanged(object sender, NotifyCollectionChangedEventArgs e) { - ISkinnableComponent[] skinnableComponents = target.ChildrenOfType().ToArray(); + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (var item in e.NewItems.Cast()) + AddBlueprintFor(item); + break; - foreach (var c in skinnableComponents) - AddBlueprintFor(c); + case NotifyCollectionChangedAction.Remove: + case NotifyCollectionChangedAction.Reset: + foreach (var item in e.OldItems.Cast()) + RemoveBlueprintFor(item); + break; - // 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); + case NotifyCollectionChangedAction.Replace: + foreach (var item in e.OldItems.Cast()) + RemoveBlueprintFor(item); + + foreach (var item in e.NewItems.Cast()) + AddBlueprintFor(item); + break; + } } protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 90b003153b..b098136eda 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -18,6 +18,7 @@ using osuTK; namespace osu.Game.Skinning.Editor { + [Cached(typeof(SkinEditor))] public class SkinEditor : FocusedOverlayContainer { public const double TRANSITION_DURATION = 500; @@ -28,11 +29,15 @@ namespace osu.Game.Skinning.Editor protected override bool StartHidden => true; + public readonly BindableList SelectedComponents = new BindableList(); + [Resolved] private SkinManager skins { get; set; } private Bindable currentSkin; + private SkinBlueprintContainer blueprintContainer; + public SkinEditor(Drawable targetScreen) { this.targetScreen = targetScreen; @@ -56,7 +61,7 @@ namespace osu.Game.Skinning.Editor Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X }, - new SkinBlueprintContainer(targetScreen), + blueprintContainer = new SkinBlueprintContainer(targetScreen), new SkinComponentToolbox(600) { Anchor = Anchor.CentreLeft, @@ -109,9 +114,15 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - Drawable instance = (Drawable)Activator.CreateInstance(type); + var instance = (Drawable)Activator.CreateInstance(type) as ISkinnableComponent; + + if (instance == null) + throw new InvalidOperationException("Attempted to instantiate a component for placement which was not an {typeof(ISkinnableComponent)}."); getTarget(SkinnableTarget.MainHUDComponents)?.Add(instance); + + SelectedComponents.Clear(); + SelectedComponents.Add(instance); } private ISkinnableTarget getTarget(SkinnableTarget target) From 1831f581aa15f14f4cd01bf73122dedc5a5d804b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 18:07:58 +0900 Subject: [PATCH 510/708] Add basic metadata display and remove outdated message about not saving --- osu.Game/Skinning/Editor/SkinEditor.cs | 55 ++++++++++++++++---------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index b098136eda..0e243d1a02 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -36,7 +36,8 @@ namespace osu.Game.Skinning.Editor private Bindable currentSkin; - private SkinBlueprintContainer blueprintContainer; + [Resolved] + private OsuColour colours { get; set; } public SkinEditor(Drawable targetScreen) { @@ -46,7 +47,7 @@ namespace osu.Game.Skinning.Editor } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChild = new OsuContextMenuContainer { @@ -61,7 +62,7 @@ namespace osu.Game.Skinning.Editor Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X }, - blueprintContainer = new SkinBlueprintContainer(targetScreen), + new SkinBlueprintContainer(targetScreen), new SkinComponentToolbox(600) { Anchor = Anchor.CentreLeft, @@ -103,13 +104,42 @@ namespace osu.Game.Skinning.Editor }, } }; + } - 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 => + protected override void LoadComplete() + { + base.LoadComplete(); + + Show(); + + // as long as the skin editor is loaded, let's make sure we can modify the current skin. + currentSkin = skins.CurrentSkin.GetBoundCopy(); + + // schedule ensures this only happens when the skin editor is visible. + // also avoid some weird endless recursion / bindable feedback loop (something to do with tracking skins across three different bindable types). + // probably something which will be factored out in a future database refactor so not too concerning for now. + currentSkin.BindValueChanged(skin => Scheduler.AddOnce(skinChanged), true); + } + + private void skinChanged() + { + headerText.Clear(); + + headerText.AddParagraph("Skin editor", cp => cp.Font = OsuFont.Default.With(size: 24)); + headerText.NewParagraph(); + headerText.AddText("Currently editing ", cp => { cp.Font = OsuFont.Default.With(size: 12); cp.Colour = colours.Yellow; }); + + headerText.AddText($"{currentSkin.Value.SkinInfo}", cp => + { + cp.Font = OsuFont.Default.With(size: 12, weight: FontWeight.Bold); + cp.Colour = colours.Yellow; + }); + + skins.EnsureMutableSkin(); } private void placeComponent(Type type) @@ -130,21 +160,6 @@ namespace osu.Game.Skinning.Editor return targetScreen.ChildrenOfType().FirstOrDefault(c => c.Target == target); } - protected override void LoadComplete() - { - base.LoadComplete(); - - Show(); - - // as long as the skin editor is loaded, let's make sure we can modify the current skin. - currentSkin = skins.CurrentSkin.GetBoundCopy(); - - // schedule ensures this only happens when the skin editor is visible. - // also avoid some weird endless recursion / bindable feedback loop (something to do with tracking skins across three different bindable types). - // probably something which will be factored out in a future database refactor so not too concerning for now. - currentSkin.BindValueChanged(skin => Scheduler.AddOnce(skins.EnsureMutableSkin), true); - } - private void revert() { SkinnableElementTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); From 6d587dc39229c8150e6f563ec5122b81115f5edc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 18:17:05 +0900 Subject: [PATCH 511/708] Adjust target size slightly to better align with the screen --- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index cc989bb459..cbed498a38 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -71,7 +71,7 @@ namespace osu.Game.Skinning.Editor target.RelativePositionAxes = Axes.Both; target.ScaleTo(VISIBLE_TARGET_SCALE, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); - target.MoveToX(0.1f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); + target.MoveToX(0.095f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); } else { From f55407f871a94a5846270f947b1326ecdb9de8bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 18:37:41 +0900 Subject: [PATCH 512/708] Show a message when attempting to customisse a screen which doesn't support it --- osu.Game/Screens/ScreenWhiteBox.cs | 24 ++--- .../Skinning/Editor/SkinBlueprintContainer.cs | 14 ++- osu.Game/Skinning/Editor/SkinEditor.cs | 90 ++++++++++++------- 3 files changed, 81 insertions(+), 47 deletions(-) diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index 3d8fd5dad7..cf0c183766 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -87,9 +87,9 @@ namespace osu.Game.Screens private static Color4 getColourFor(object type) { int hash = type.GetHashCode(); - byte r = (byte)Math.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255); - byte g = (byte)Math.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255); - byte b = (byte)Math.Clamp((hash & 0x0000FF) * 0.8f, 20, 255); + byte r = (byte)Math.Clamp(((hash & 0xFF0000) >> 16) * 2, 128, 255); + byte g = (byte)Math.Clamp(((hash & 0x00FF00) >> 8) * 2, 128, 255); + byte b = (byte)Math.Clamp((hash & 0x0000FF) * 2, 128, 255); return new Color4(r, g, b, 255); } @@ -109,10 +109,10 @@ namespace osu.Game.Screens private readonly Container boxContainer; - public UnderConstructionMessage(string name) + public UnderConstructionMessage(string name, string description = "is not yet ready for use!") { - RelativeSizeAxes = Axes.Both; - Size = new Vector2(0.3f); + AutoSizeAxes = Axes.Both; + Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -124,7 +124,7 @@ namespace osu.Game.Screens { CornerRadius = 20, Masking = true, - RelativeSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, Children = new Drawable[] @@ -133,15 +133,15 @@ namespace osu.Game.Screens { RelativeSizeAxes = Axes.Both, - Colour = colour, - Alpha = 0.2f, - Blending = BlendingParameters.Additive, + Colour = colour.Darken(0.8f), + Alpha = 0.8f, }, TextContainer = new FillFlowContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, + Padding = new MarginPadding(20), Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -157,14 +157,14 @@ namespace osu.Game.Screens Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = name, - Colour = colour.Lighten(0.8f), + Colour = colour, Font = OsuFont.GetFont(size: 36), }, new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = "is not yet ready for use!", + Text = description, Font = OsuFont.GetFont(size: 20), }, new OsuSpriteText diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index bc1ea02090..ef189ed165 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -7,8 +7,10 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Rulesets.Edit; +using osu.Game.Screens; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Play.HUD; @@ -36,7 +38,17 @@ namespace osu.Game.Skinning.Editor base.LoadComplete(); // track each target container on the current screen. - foreach (var targetContainer in target.ChildrenOfType()) + var targetContainers = target.ChildrenOfType().ToArray(); + + if (targetContainers.Length == 0) + { + var targetScreen = target.ChildrenOfType().LastOrDefault()?.GetType().Name ?? "this screen"; + + AddInternal(new ScreenWhiteBox.UnderConstructionMessage(targetScreen, "doesn't support skin customisation just yet.")); + return; + } + + foreach (var targetContainer in targetContainers) { var bindableList = new BindableList { BindTarget = targetContainer.Components }; bindableList.BindCollectionChanged(componentsChanged, true); diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 0e243d1a02..08fc458b94 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -62,46 +62,68 @@ namespace osu.Game.Skinning.Editor Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X }, - new SkinBlueprintContainer(targetScreen), - new SkinComponentToolbox(600) + new GridContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RequestPlacement = placeComponent - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Spacing = new Vector2(5), - Padding = new MarginPadding + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - Top = 10, - Left = 10, + new Dimension(GridSizeMode.AutoSize), + new Dimension() }, - Margin = new MarginPadding + Content = new[] { - Right = 10, - Bottom = 10, - }, - Children = new Drawable[] - { - new TriangleButton + new Drawable[] { - Text = "Save Changes", - Width = 140, - Action = save, - }, - new DangerousTriangleButton - { - Text = "Revert to default", - Width = 140, - Action = revert, - }, + new SkinComponentToolbox(600) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RequestPlacement = placeComponent + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SkinBlueprintContainer(targetScreen), + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Spacing = new Vector2(5), + Padding = new MarginPadding + { + Top = 10, + Left = 10, + }, + Margin = new MarginPadding + { + Right = 10, + Bottom = 10, + }, + Children = new Drawable[] + { + new TriangleButton + { + Text = "Save Changes", + Width = 140, + Action = save, + }, + new DangerousTriangleButton + { + Text = "Revert to default", + Width = 140, + Action = revert, + }, + } + }, + } + }, + } } - }, + } } }; } From 4bb933e4b111439ac022ed82c713b0197845c469 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 18:55:45 +0900 Subject: [PATCH 513/708] Add missing base lookup call to `DefaultSkin` --- osu.Game/Skinning/DefaultSkin.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 1c063b6ef2..3de3dc7702 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -36,6 +36,9 @@ namespace osu.Game.Skinning public override Drawable GetDrawableComponent(ISkinComponent component) { + if (base.GetDrawableComponent(component) is Drawable c) + return c; + switch (component) { case SkinnableTargetComponent target: From 1231c08a0791ec9b739303f29ce3bc2aa9a36906 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 18:58:26 +0900 Subject: [PATCH 514/708] Rename mismatching file --- .../{IImmutableSkinnableComponent.cs => ISkinSerialisable.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game/Skinning/{IImmutableSkinnableComponent.cs => ISkinSerialisable.cs} (100%) diff --git a/osu.Game/Skinning/IImmutableSkinnableComponent.cs b/osu.Game/Skinning/ISkinSerialisable.cs similarity index 100% rename from osu.Game/Skinning/IImmutableSkinnableComponent.cs rename to osu.Game/Skinning/ISkinSerialisable.cs From 811282a975ca48c91239599c0521dbe60a3811f8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 19:01:41 +0900 Subject: [PATCH 515/708] Add failing test --- .../Multiplayer/TestSceneMultiplayer.cs | 93 +++++++++++++++++++ .../Multiplayer/TestMultiplayerClient.cs | 10 +- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 78bc51e47b..519d76fc93 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -1,9 +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 System.Linq; +using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Platform; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; +using osu.Game.Users; +using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { @@ -11,7 +26,85 @@ namespace osu.Game.Tests.Visual.Multiplayer { private TestMultiplayer multiplayerScreen; + private BeatmapManager beatmaps; + private RulesetStore rulesets; + private BeatmapSetInfo importedSet; + + private TestMultiplayerClient client => multiplayerScreen.Client; + private Room room => client.APIRoom; + public TestSceneMultiplayer() + { + loadMultiplayer(); + } + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + } + + [Test] + public void TestMatchStartWhileSpectatingWithNoBeatmap() + { + loadMultiplayer(); + + AddStep("open room", () => + { + multiplayerScreen.OpenNewRoom(new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + }); + + AddStep("create room", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for join", () => client.Room != null); + + AddStep("join other user (ready, host)", () => + { + client.AddUser(new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Other" }); + client.TransferHost(MultiplayerTestScene.PLAYER_1_ID); + client.ChangeUserState(MultiplayerTestScene.PLAYER_1_ID, MultiplayerUserState.Ready); + }); + + AddStep("change playlist", () => + { + room.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + }); + }); + + AddStep("click spectate button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("start match externally", () => client.StartMatch()); + + AddAssert("screen not changed", () => multiplayerScreen.IsCurrentScreen()); + } + + private void loadMultiplayer() { AddStep("show", () => { diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index de77a15da0..167cf705a7 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 Room? APIRoom { get; private set; } + public Action? RoomSetupAction; [Resolved] @@ -138,10 +140,16 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomSetupAction?.Invoke(room); RoomSetupAction = null; + APIRoom = apiRoom; + return Task.FromResult(room); } - protected override Task LeaveRoomInternal() => Task.CompletedTask; + protected override Task LeaveRoomInternal() + { + APIRoom = null; + return Task.CompletedTask; + } public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(userId); From 7fe8737d9437073a6c6137483d6c9be48cf3c9c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 19:21:44 +0900 Subject: [PATCH 516/708] Add failing tests --- .../Multiplayer/TestSceneMultiplayer.cs | 92 ++++++++++++++----- 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 519d76fc93..a81927bec2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -49,34 +49,23 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestMatchStartWhileSpectatingWithNoBeatmap() + public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap() { loadMultiplayer(); - AddStep("open room", () => + createRoom(new Room { - multiplayerScreen.OpenNewRoom(new Room + Name = { Value = "Test Room" }, + Playlist = { - Name = { Value = "Test Room" }, - Playlist = + new PlaylistItem { - new PlaylistItem - { - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, - } + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, } - }); + } }); - AddStep("create room", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - - AddUntilStep("wait for join", () => client.Room != null); - AddStep("join other user (ready, host)", () => { client.AddUser(new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Other" }); @@ -84,13 +73,44 @@ namespace osu.Game.Tests.Visual.Multiplayer client.ChangeUserState(MultiplayerTestScene.PLAYER_1_ID, MultiplayerUserState.Ready); }); - AddStep("change playlist", () => + AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); + + AddStep("click spectate button", () => { - room.Playlist.Add(new PlaylistItem + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("start match externally", () => client.StartMatch()); + + AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen()); + } + + [Test] + public void TestLocalPlayStartsWhileSpectatingWhenBeatmapBecomesAvailable() + { + loadMultiplayer(); + + createRoom(new Room + { + Name = { Value = "Test Room" }, + Playlist = { - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, - }); + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); + + AddStep("join other user (ready, host)", () => + { + client.AddUser(new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Other" }); + client.TransferHost(MultiplayerTestScene.PLAYER_1_ID); + client.ChangeUserState(MultiplayerTestScene.PLAYER_1_ID, MultiplayerUserState.Ready); }); AddStep("click spectate button", () => @@ -101,7 +121,29 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("start match externally", () => client.StartMatch()); - AddAssert("screen not changed", () => multiplayerScreen.IsCurrentScreen()); + AddStep("restore beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + }); + + AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen()); + } + + private void createRoom(Room room) + { + AddStep("open room", () => + { + multiplayerScreen.OpenNewRoom(room); + }); + + AddStep("create room", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for join", () => client.Room != null); } private void loadMultiplayer() From 9ad1e5067e0494730446a34bc01e0222010e438a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 19:22:09 +0900 Subject: [PATCH 517/708] Fix spectate being entered while not having the beatmap --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index ab22d40181..fa18b792c3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -14,6 +14,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays; @@ -354,10 +355,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.ChangeBeatmapAvailability(availability.NewValue); - // while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap. - if (availability.NewValue != Online.Rooms.BeatmapAvailability.LocallyAvailable() - && client.LocalUser?.State == MultiplayerUserState.Ready) - client.ChangeState(MultiplayerUserState.Idle); + if (availability.NewValue.State != DownloadState.LocallyAvailable) + { + // while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap. + if (client.LocalUser?.State == MultiplayerUserState.Ready) + client.ChangeState(MultiplayerUserState.Idle); + } + else + { + if (client.LocalUser?.State == MultiplayerUserState.Spectating && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) + onLoadRequested(); + } } private void onReadyClick() @@ -413,6 +421,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onLoadRequested() { + if (BeatmapAvailability.Value.State != DownloadState.LocallyAvailable) + return; + // In the case of spectating, IMultiplayerClient.LoadRequested can be fired while the game is still spectating a previous session. // For now, we want to game to switch to the new game so need to request exiting from the play screen. if (!ParentScreen.IsCurrentScreen()) From bc4213eea1fd86a2184d1c0ba8d874025e9c3ce6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 19:26:58 +0900 Subject: [PATCH 518/708] Add test for changing back to idle on deletion --- .../Multiplayer/TestSceneMultiplayer.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index a81927bec2..f2331542c6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK.Input; @@ -43,9 +42,37 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); - beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + } + [SetUp] + public void Setup() => Schedule(() => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + }); + + [Test] + public void TestUserSetToIdleWhenBeatmapDeleted() + { + loadMultiplayer(); + + createRoom(new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("set user ready", () => client.ChangeState(MultiplayerUserState.Ready)); + AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); + + AddAssert("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle); } [Test] From f5bc389998283eca1a7e4d0363945f1800840aa7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 11 May 2021 19:31:32 +0900 Subject: [PATCH 519/708] Fix flaky tests --- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index f2331542c6..69fbd56490 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -14,6 +15,7 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Resources; using osu.Game.Users; @@ -56,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { loadMultiplayer(); - createRoom(new Room + createRoom(() => new Room { Name = { Value = "Test Room" }, Playlist = @@ -80,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { loadMultiplayer(); - createRoom(new Room + createRoom(() => new Room { Name = { Value = "Test Room" }, Playlist = @@ -118,7 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { loadMultiplayer(); - createRoom(new Room + createRoom(() => new Room { Name = { Value = "Test Room" }, Playlist = @@ -157,13 +159,16 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("play started", () => !multiplayerScreen.IsCurrentScreen()); } - private void createRoom(Room room) + private void createRoom(Func room) { AddStep("open room", () => { - multiplayerScreen.OpenNewRoom(room); + multiplayerScreen.OpenNewRoom(room()); }); + AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddWaitStep("wait for transition", 2); + AddStep("create room", () => { InputManager.MoveMouseTo(this.ChildrenOfType().Single()); From 048677846bfd5cb7ef8a8e186b9821e03ebca5d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 12:21:31 +0900 Subject: [PATCH 520/708] Change `HealthDisplay` to be a `CompositeDrawable` --- osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs | 6 ++++-- osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs | 2 +- osu.Game/Screens/Play/HUD/FailingLayer.cs | 2 +- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index fb4c9d713a..c335f7c99e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -50,9 +52,9 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddStep("set health to 0.10", () => layer.Current.Value = 0.1); - AddUntilStep("layer fade is visible", () => layer.Child.Alpha > 0.1f); + AddUntilStep("layer fade is visible", () => layer.ChildrenOfType().First().Alpha > 0.1f); AddStep("set health to 1", () => layer.Current.Value = 1f); - AddUntilStep("layer fade is invisible", () => !layer.Child.IsPresent); + AddUntilStep("layer fade is invisible", () => !layer.ChildrenOfType().First().IsPresent); } [Test] diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index 37bd289aee..241777244b 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play.HUD RelativeSizeAxes = Axes.X; Margin = new MarginPadding { Top = 20 }; - Children = new Drawable[] + InternalChildren = new Drawable[] { new Box { diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index e071337f34..424ee55766 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play.HUD public FailingLayer() { RelativeSizeAxes = Axes.Both; - Children = new Drawable[] + InternalChildren = new Drawable[] { boxes = new Container { diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 6c2571cc28..b970ecd1c7 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.HUD /// A container for components displaying the current player health. /// Gets bound automatically to the when inserted to hierarchy. /// - public abstract class HealthDisplay : Container + public abstract class HealthDisplay : CompositeDrawable { [Resolved] protected HealthProcessor HealthProcessor { get; private set; } From 8e226319e2603b0fc8aae7c645c6709fcef19f85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 15:40:33 +0900 Subject: [PATCH 521/708] Remove downwards dependency from `HUDOverlay` to `HealthDisplay` --- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 13 +++++++++++-- osu.Game/Screens/Play/HUDOverlay.cs | 4 +--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 6c2571cc28..e0ddde026b 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -16,6 +17,8 @@ namespace osu.Game.Screens.Play.HUD /// public abstract class HealthDisplay : Container { + private Bindable showHealthbar; + [Resolved] protected HealthProcessor HealthProcessor { get; private set; } @@ -29,12 +32,18 @@ namespace osu.Game.Screens.Play.HUD { } - [BackgroundDependencyLoader] - private void load() + [BackgroundDependencyLoader(true)] + private void load(HUDOverlay hud) { Current.BindTo(HealthProcessor.Health); HealthProcessor.NewJudgement += onNewJudgement; + + if (hud != null) + { + showHealthbar = hud.ShowHealthbar.GetBoundCopy(); + showHealthbar.BindValueChanged(healthBar => this.FadeTo(healthBar.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true); + } } private void onNewJudgement(JudgementResult judgement) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 6ddaa338cc..c9b80be3dc 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -36,7 +36,6 @@ namespace osu.Game.Screens.Play public readonly KeyCounterDisplay KeyCounter; public readonly SkinnableScoreCounter ScoreCounter; public readonly SkinnableAccuracyCounter AccuracyCounter; - public readonly SkinnableHealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; @@ -96,7 +95,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - HealthDisplay = CreateHealthDisplay(), + CreateHealthDisplay(), AccuracyCounter = CreateAccuracyCounter(), ScoreCounter = CreateScoreCounter(), CreateComboCounter(), @@ -185,7 +184,6 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - ShowHealthbar.BindValueChanged(healthBar => HealthDisplay.FadeTo(healthBar.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING), true); ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING))); IsBreakTime.BindValueChanged(_ => updateVisibility()); From 77e422409cdc9e6b216724f778c2b32617878ebf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 May 2021 17:00:24 +0900 Subject: [PATCH 522/708] Add `SkinInfo.InstantiationInfo` to allow creating different skin types --- .../TestSceneHitCircleArea.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- ...60743_AddSkinInstantiationInfo.Designer.cs | 508 ++++++++++++++++++ ...20210511060743_AddSkinInstantiationInfo.cs | 22 + .../Migrations/OsuDbContextModelSnapshot.cs | 6 +- osu.Game/Skinning/DefaultLegacySkin.cs | 10 +- osu.Game/Skinning/DefaultSkin.cs | 10 +- osu.Game/Skinning/SkinInfo.cs | 36 +- osu.Game/Skinning/SkinManager.cs | 31 +- 9 files changed, 597 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs create mode 100644 osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs index 0649989dc0..1fdcd73dde 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = new SkinProvidingContainer(new DefaultSkin()) + Child = new SkinProvidingContainer(new DefaultSkin(null)) { RelativeSizeAxes = Axes.Both, Child = drawableHitCircle = new DrawableHitCircle(hitCircle) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index e0eeaf6db0..3576b149bf 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps public bool SkinLoaded => skin.IsResultAvailable; public ISkin Skin => skin.Value; - protected virtual ISkin GetSkin() => new DefaultSkin(); + protected virtual ISkin GetSkin() => new DefaultSkin(null); private readonly RecyclableLazy skin; public abstract Stream GetStream(string storagePath); diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs new file mode 100644 index 0000000000..b808c648da --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210511060743_AddSkinInstantiationInfo")] + partial class AddSkinInstantiationInfo + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs new file mode 100644 index 0000000000..1d5b0769a4 --- /dev/null +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInstantiationInfo : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "InstantiationInfo", + table: "SkinInfo", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "InstantiationInfo", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index ec4461ca56..d4bde50b60 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -141,8 +141,6 @@ namespace osu.Game.Migrations b.Property("TitleUnicode"); - b.Property("VideoFile"); - b.HasKey("ID"); b.ToTable("BeatmapMetadata"); @@ -352,7 +350,7 @@ namespace osu.Game.Migrations b.Property("TotalScore"); - b.Property("UserID") + b.Property("UserID") .HasColumnName("UserID"); b.Property("UserString") @@ -402,6 +400,8 @@ namespace osu.Game.Migrations b.Property("Hash"); + b.Property("InstantiationInfo"); + b.Property("Name"); b.HasKey("ID"); diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 4027cc650d..564be8630e 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -10,7 +10,12 @@ namespace osu.Game.Skinning public class DefaultLegacySkin : LegacySkin { public DefaultLegacySkin(IResourceStore storage, IStorageResourceProvider resources) - : base(Info, storage, resources, string.Empty) + : this(Info, storage, resources) + { + } + + public DefaultLegacySkin(SkinInfo skin, IResourceStore storage, IStorageResourceProvider resources) + : base(skin, storage, resources, string.Empty) { Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); Configuration.AddComboColours( @@ -27,7 +32,8 @@ namespace osu.Game.Skinning { ID = SkinInfo.CLASSIC_SKIN, // this is temporary until database storage is decided upon. Name = "osu!classic", - Creator = "team osu!" + Creator = "team osu!", + InstantiationInfo = typeof(DefaultLegacySkin).AssemblyQualifiedName, }; } } diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 0b3f5f3cde..fcd874d6ca 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -8,14 +8,20 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.IO; using osuTK.Graphics; namespace osu.Game.Skinning { public class DefaultSkin : Skin { - public DefaultSkin() - : base(SkinInfo.Default) + public DefaultSkin(IStorageResourceProvider resources) + : this(SkinInfo.Default, resources) + { + } + + public DefaultSkin(SkinInfo skin, IStorageResourceProvider resources) + : base(skin) { Configuration = new DefaultSkinConfiguration(); } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index aaccbefb3d..2e29808cb4 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Linq; +using osu.Framework.IO.Stores; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.IO; namespace osu.Game.Skinning { @@ -22,7 +25,35 @@ namespace osu.Game.Skinning public string Creator { get; set; } - public List Files { get; set; } + private string instantiationInfo; + + public string InstantiationInfo + { + get => instantiationInfo; + set => instantiationInfo = abbreviateInstantiationInfo(value); + } + + private string abbreviateInstantiationInfo(string value) + { + // exclude version onwards, matching only on namespace and type. + // this is mainly to allow for new versions of already loaded rulesets to "upgrade" from old. + return string.Join(',', value.Split(',').Take(2)); + } + + public virtual Skin CreateInstance(IResourceStore legacyDefaultResources, IStorageResourceProvider resources) + { + var type = string.IsNullOrEmpty(InstantiationInfo) + // handle the case of skins imported before InstantiationInfo was added. + ? typeof(LegacySkin) + : Type.GetType(InstantiationInfo); + + if (type == typeof(DefaultLegacySkin)) + return (Skin)Activator.CreateInstance(type, this, legacyDefaultResources, resources); + + return (Skin)Activator.CreateInstance(type, this, resources); + } + + public List Files { get; set; } = new List(); public List Settings { get; set; } @@ -32,7 +63,8 @@ namespace osu.Game.Skinning { ID = DEFAULT_SKIN, Name = "osu!lazer", - Creator = "team osu!" + Creator = "team osu!", + InstantiationInfo = typeof(DefaultSkin).AssemblyQualifiedName, }; public bool Equals(SkinInfo other) => other != null && ID == other.ID; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ac4d63159a..dbb9dcb7fc 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -36,7 +36,7 @@ namespace osu.Game.Skinning private readonly IResourceStore legacyDefaultResources; - public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin()); + public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin(null)); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; public override IEnumerable HandledExtensions => new[] { ".osk" }; @@ -105,7 +105,7 @@ namespace osu.Game.Skinning { // we need to populate early to create a hash based off skin.ini contents if (item.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) - populateMetadata(item); + populateMetadata(item, GetSkin(item)); if (item.Creator != null && item.Creator != unknown_creator_string) { @@ -122,18 +122,20 @@ namespace osu.Game.Skinning { await base.Populate(model, archive, cancellationToken).ConfigureAwait(false); + var instance = GetSkin(model); + + model.InstantiationInfo ??= instance.GetType().AssemblyQualifiedName; + if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) - populateMetadata(model); + populateMetadata(model, instance); } - private void populateMetadata(SkinInfo item) + private void populateMetadata(SkinInfo item, Skin instance) { - Skin reference = GetSkin(item); - - if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) + if (!string.IsNullOrEmpty(instance.Configuration.SkinInfo.Name)) { - item.Name = reference.Configuration.SkinInfo.Name; - item.Creator = reference.Configuration.SkinInfo.Creator; + item.Name = instance.Configuration.SkinInfo.Name; + item.Creator = instance.Configuration.SkinInfo.Creator; } else { @@ -147,16 +149,7 @@ namespace osu.Game.Skinning /// /// The skin to lookup. /// A instance correlating to the provided . - public Skin GetSkin(SkinInfo skinInfo) - { - if (skinInfo == SkinInfo.Default) - return new DefaultSkin(); - - if (skinInfo == DefaultLegacySkin.Info) - return new DefaultLegacySkin(legacyDefaultResources, this); - - return new LegacySkin(skinInfo, this); - } + public Skin GetSkin(SkinInfo skinInfo) => skinInfo.CreateInstance(legacyDefaultResources, this); /// /// Perform a lookup query on available s. From d706073e014c1849cabf2b252a292188b26f8173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 May 2021 23:08:50 +0200 Subject: [PATCH 523/708] Trim empty remarks xmldoc tag --- osu.Game.Rulesets.Mania/UI/Column.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index fcbbd8a01c..88c3bd5dbf 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -31,9 +31,6 @@ namespace osu.Game.Rulesets.Mania.UI /// For hitsounds played by this (i.e. not as a result of hitting a hitobject), /// a certain number of samples are allowed to be played concurrently so that it feels better when spam-pressing the key. /// - /// - /// - /// private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY; /// From 97bd482d4dae632548438ec05315ac37aec8c8fa Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 01:21:38 +0200 Subject: [PATCH 524/708] Factor out `load` from settings into new `Settings` class --- osu.Game/Screens/Edit/Settings.cs | 44 +++++++++++++++++++ .../Edit/Timing/ControlPointSettings.cs | 35 +-------------- osu.Game/Screens/Edit/Verify/IssueSettings.cs | 32 +------------- 3 files changed, 48 insertions(+), 63 deletions(-) create mode 100644 osu.Game/Screens/Edit/Settings.cs diff --git a/osu.Game/Screens/Edit/Settings.cs b/osu.Game/Screens/Edit/Settings.cs new file mode 100644 index 0000000000..758414333d --- /dev/null +++ b/osu.Game/Screens/Edit/Settings.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit +{ + public abstract class Settings : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colours) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Background4, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = CreateSections() + }, + } + }; + } + + protected abstract IReadOnlyList CreateSections(); + } +} diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 921fa675b3..36f31d4be4 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -2,44 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; -using osu.Game.Overlays; namespace osu.Game.Screens.Edit.Timing { - public class ControlPointSettings : CompositeDrawable + public class ControlPointSettings : Settings { - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours) - { - RelativeSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.Background4, - RelativeSizeAxes = Axes.Both, - }, - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = createSections() - }, - } - }; - } - - private IReadOnlyList createSections() => new Drawable[] + protected override IReadOnlyList CreateSections() => new Drawable[] { new GroupSection(), new TimingSection(), diff --git a/osu.Game/Screens/Edit/Verify/IssueSettings.cs b/osu.Game/Screens/Edit/Verify/IssueSettings.cs index 4519231cd2..15fc54d64f 100644 --- a/osu.Game/Screens/Edit/Verify/IssueSettings.cs +++ b/osu.Game/Screens/Edit/Verify/IssueSettings.cs @@ -2,44 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Edit.Verify { - public class IssueSettings : CompositeDrawable + public class IssueSettings : Settings { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.Both; - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.Gray3, - RelativeSizeAxes = Axes.Both, - }, - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = createSections() - }, - } - }; } - private IReadOnlyList createSections() => new Drawable[] + protected override IReadOnlyList CreateSections() => new Drawable[] { }; } From d3c1ec55eec7399ea8405d1f39ee92a6f9b2fca6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 01:22:32 +0200 Subject: [PATCH 525/708] Take `IssueList` in `IssueSettings` constructor We'll be using this for bindables later. --- osu.Game/Screens/Edit/Verify/IssueSettings.cs | 4 ++++ osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueSettings.cs b/osu.Game/Screens/Edit/Verify/IssueSettings.cs index 15fc54d64f..be06700b28 100644 --- a/osu.Game/Screens/Edit/Verify/IssueSettings.cs +++ b/osu.Game/Screens/Edit/Verify/IssueSettings.cs @@ -8,7 +8,11 @@ namespace osu.Game.Screens.Edit.Verify { public class IssueSettings : Settings { + private readonly IssueList issueList; + public IssueSettings(IssueList issueList) + { + this.issueList = issueList; } protected override IReadOnlyList CreateSections() => new Drawable[] diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 9de1f04271..9fb81a6681 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -30,6 +30,8 @@ namespace osu.Game.Screens.Edit.Verify [BackgroundDependencyLoader] private void load() { + IssueList issueList; + Child = new Container { RelativeSizeAxes = Axes.Both, @@ -45,8 +47,8 @@ namespace osu.Game.Screens.Edit.Verify { new Drawable[] { - new IssueList(), - new IssueSettings(), + issueList = new IssueList(), + new IssueSettings(issueList), }, } } From 1de35f880b8b571e7aa89c597c69a8dcd1dd8dde Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 01:23:31 +0200 Subject: [PATCH 526/708] Separate `IssueList` into own class --- osu.Game/Screens/Edit/Verify/IssueList.cs | 100 +++++++++++++++++++ osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 88 ---------------- 2 files changed, 100 insertions(+), 88 deletions(-) create mode 100644 osu.Game/Screens/Edit/Verify/IssueList.cs diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs new file mode 100644 index 0000000000..6d319eb09e --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; +using osuTK; + +namespace osu.Game.Screens.Edit.Verify +{ + public class IssueList : CompositeDrawable + { + private IssueTable table; + + [Resolved] + private EditorClock clock { get; set; } + + [Resolved] + private IBindable workingBeatmap { get; set; } + + [Resolved] + private EditorBeatmap beatmap { get; set; } + + [Resolved] + private Bindable selectedIssue { get; set; } + + private IBeatmapVerifier rulesetVerifier; + private BeatmapVerifier generalVerifier; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colours) + { + generalVerifier = new BeatmapVerifier(); + rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); + + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Background2, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = table = new IssueTable(), + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding(20), + Children = new Drawable[] + { + new TriangleButton + { + Text = "Refresh", + Action = refresh, + Size = new Vector2(120, 40), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + refresh(); + } + + private void refresh() + { + var issues = generalVerifier.Run(beatmap, workingBeatmap.Value); + + if (rulesetVerifier != null) + issues = issues.Concat(rulesetVerifier.Run(beatmap, workingBeatmap.Value)); + + table.Issues = issues + .OrderBy(issue => issue.Template.Type) + .ThenBy(issue => issue.Check.Metadata.Category); + } + } +} diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 9fb81a6681..ed4658da8e 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks.Components; -using osuTK; namespace osu.Game.Screens.Edit.Verify { @@ -54,85 +46,5 @@ namespace osu.Game.Screens.Edit.Verify } }; } - - public class IssueList : CompositeDrawable - { - private IssueTable table; - - [Resolved] - private EditorClock clock { get; set; } - - [Resolved] - private IBindable workingBeatmap { get; set; } - - [Resolved] - private EditorBeatmap beatmap { get; set; } - - [Resolved] - private Bindable selectedIssue { get; set; } - - private IBeatmapVerifier rulesetVerifier; - private BeatmapVerifier generalVerifier; - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours) - { - generalVerifier = new BeatmapVerifier(); - rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); - - RelativeSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.Background2, - RelativeSizeAxes = Axes.Both, - }, - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = table = new IssueTable(), - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Margin = new MarginPadding(20), - Children = new Drawable[] - { - new TriangleButton - { - Text = "Refresh", - Action = refresh, - Size = new Vector2(120, 40), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - } - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - refresh(); - } - - private void refresh() - { - var issues = generalVerifier.Run(beatmap, workingBeatmap.Value); - - if (rulesetVerifier != null) - issues = issues.Concat(rulesetVerifier.Run(beatmap, workingBeatmap.Value)); - - table.Issues = issues - .OrderBy(issue => issue.Template.Type) - .ThenBy(issue => issue.Check.Metadata.Category); - } - } } } From 01b87947579c417395458c2832ba730d331d6e6b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 01:26:12 +0200 Subject: [PATCH 527/708] Add abstract `Section` class Similar to `Section` in the timing screen, but does not make use of checkboxes, nor specific to control points. So there's a lot of things that differ, hence new class instead of factoring that out. --- osu.Game/Screens/Edit/Verify/Section.cs | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 osu.Game/Screens/Edit/Verify/Section.cs diff --git a/osu.Game/Screens/Edit/Verify/Section.cs b/osu.Game/Screens/Edit/Verify/Section.cs new file mode 100644 index 0000000000..f6815a05a1 --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/Section.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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Verify +{ + public abstract class Section : CompositeDrawable + { + private const int header_height = 50; + + protected readonly IssueList IssueList; + + protected FillFlowContainer Flow; + protected abstract string Header { get; } + + protected Section(IssueList issueList) + { + IssueList = issueList; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colours) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height, + Padding = new MarginPadding { Horizontal = 20 }, + Child = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = Header, + Font = new FontUsage(size: 25, weight: "bold") + } + }, + new Container + { + Y = header_height, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = Flow = new FillFlowContainer + { + Padding = new MarginPadding { Horizontal = 20 }, + Spacing = new Vector2(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + } + } + }; + } + } +} From 2e4399f0c1f4bcb5cc66bf755f0e4954ab5c6b66 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 01:27:21 +0200 Subject: [PATCH 528/708] Add `VisibilitySection` and its bindables in `IssueList` --- osu.Game/Screens/Edit/Verify/IssueList.cs | 10 +++++ osu.Game/Screens/Edit/Verify/IssueSettings.cs | 1 + .../Screens/Edit/Verify/VisibilitySection.cs | 38 +++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 osu.Game/Screens/Edit/Verify/VisibilitySection.cs diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 6d319eb09e..dd1dffcb42 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -34,12 +34,22 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] private Bindable selectedIssue { get; set; } + public Dictionary> ShowType { get; set; } + private IBeatmapVerifier rulesetVerifier; private BeatmapVerifier generalVerifier; [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { + // Reflects the user interface. Only types in this dictionary have configurable visibility. + ShowType = new Dictionary> + { + { IssueType.Warning, new Bindable(true) }, + { IssueType.Error, new Bindable(true) }, + { IssueType.Negligible, new Bindable(false) } + }; + generalVerifier = new BeatmapVerifier(); rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); diff --git a/osu.Game/Screens/Edit/Verify/IssueSettings.cs b/osu.Game/Screens/Edit/Verify/IssueSettings.cs index be06700b28..0df3ab0dcb 100644 --- a/osu.Game/Screens/Edit/Verify/IssueSettings.cs +++ b/osu.Game/Screens/Edit/Verify/IssueSettings.cs @@ -17,6 +17,7 @@ namespace osu.Game.Screens.Edit.Verify protected override IReadOnlyList CreateSections() => new Drawable[] { + new VisibilitySection(issueList) }; } } diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs new file mode 100644 index 0000000000..e9fa9b56ed --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Screens.Edit.Verify +{ + internal class VisibilitySection : Section + { + public VisibilitySection(IssueList issueList) + : base(issueList) + { + } + + protected override string Header => "Visibility"; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colours) + { + foreach (IssueType issueType in IssueList.ShowType.Keys) + { + var checkbox = new SettingsCheckbox + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + LabelText = issueType.ToString() + }; + + checkbox.Current.BindTo(IssueList.ShowType[issueType]); + Flow.Add(checkbox); + } + } + } +} From 1bb7d412daf2ad534dcb3a09161f459fb698e535 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 01:29:46 +0200 Subject: [PATCH 529/708] Add `IssueList` filtering based on those bindables --- osu.Game/Screens/Edit/Verify/IssueList.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index dd1dffcb42..7a2202c198 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -102,9 +102,22 @@ namespace osu.Game.Screens.Edit.Verify if (rulesetVerifier != null) issues = issues.Concat(rulesetVerifier.Run(beatmap, workingBeatmap.Value)); + issues = filter(issues); + table.Issues = issues .OrderBy(issue => issue.Template.Type) .ThenBy(issue => issue.Check.Metadata.Category); } + + private IEnumerable filter(IEnumerable issues) + { + foreach (IssueType issueType in ShowType.Keys) + { + if (!ShowType[issueType].Value) + issues = issues.Where(issue => issue.Template.Type != issueType); + } + + return issues; + } } } From ad78aec1ef8380db80d9e57eb495fa3be2aa2c38 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 01:30:45 +0200 Subject: [PATCH 530/708] Refresh `IssueList` on changes in `VisibilitySection` --- osu.Game/Screens/Edit/Verify/IssueList.cs | 6 +++--- osu.Game/Screens/Edit/Verify/VisibilitySection.cs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 7a2202c198..b7a6d769e3 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Edit.Verify new TriangleButton { Text = "Refresh", - Action = refresh, + Action = Refresh, Size = new Vector2(120, 40), Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -92,10 +92,10 @@ namespace osu.Game.Screens.Edit.Verify { base.LoadComplete(); - refresh(); + Refresh(); } - private void refresh() + public void Refresh() { var issues = generalVerifier.Run(beatmap, workingBeatmap.Value); diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index e9fa9b56ed..f849426800 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -31,6 +31,7 @@ namespace osu.Game.Screens.Edit.Verify }; checkbox.Current.BindTo(IssueList.ShowType[issueType]); + checkbox.Current.BindValueChanged(_ => IssueList.Refresh()); Flow.Add(checkbox); } } From 75adec57ebfa8aaefcb124d3c802e2e8f69ee0a4 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 01:31:16 +0200 Subject: [PATCH 531/708] Remove negligible default hidden TODO --- osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs index 1241e058ad..1f708209fe 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs @@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// An error occurred and a complete check could not be made. Error, - // TODO: Negligible issues should be hidden by default. /// A possible mistake so minor/unlikely that it can often be safely ignored. Negligible, } From 4aeaec6ecc610e4c043881451ff6495edea6bcbe Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 01:32:18 +0200 Subject: [PATCH 532/708] Add `InterpretationSection` and its bindable in `IssueList` We'll eventually connect that bindable so that checks can access it. --- .../Edit/Verify/InterpretationSection.cs | 34 +++++++++++++++++++ osu.Game/Screens/Edit/Verify/IssueList.cs | 4 +++ osu.Game/Screens/Edit/Verify/IssueSettings.cs | 1 + 3 files changed, 39 insertions(+) create mode 100644 osu.Game/Screens/Edit/Verify/InterpretationSection.cs diff --git a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs new file mode 100644 index 0000000000..fa0bde9ddc --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Screens.Edit.Verify +{ + internal class InterpretationSection : Section + { + public InterpretationSection(IssueList issueList) + : base(issueList) + { + } + + protected override string Header => "Interpretation"; + + [BackgroundDependencyLoader] + private void load() + { + var dropdown = new SettingsEnumDropdown + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + TooltipText = "Affects checks that depend on difficulty level" + }; + dropdown.Current.BindTo(IssueList.InterpretedDifficulty); + + Flow.Add(dropdown); + } + } +} diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index b7a6d769e3..3e836c4010 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -36,6 +36,8 @@ namespace osu.Game.Screens.Edit.Verify public Dictionary> ShowType { get; set; } + public Bindable InterpretedDifficulty { get; set; } + private IBeatmapVerifier rulesetVerifier; private BeatmapVerifier generalVerifier; @@ -53,6 +55,8 @@ namespace osu.Game.Screens.Edit.Verify generalVerifier = new BeatmapVerifier(); rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); + InterpretedDifficulty = new Bindable(beatmap.BeatmapInfo.DifficultyRating); + RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] diff --git a/osu.Game/Screens/Edit/Verify/IssueSettings.cs b/osu.Game/Screens/Edit/Verify/IssueSettings.cs index 0df3ab0dcb..68ab51c3c8 100644 --- a/osu.Game/Screens/Edit/Verify/IssueSettings.cs +++ b/osu.Game/Screens/Edit/Verify/IssueSettings.cs @@ -17,6 +17,7 @@ namespace osu.Game.Screens.Edit.Verify protected override IReadOnlyList CreateSections() => new Drawable[] { + new InterpretationSection(issueList), new VisibilitySection(issueList) }; } From c13b93e6f15254845e62046551b5e17802915826 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 02:29:18 +0200 Subject: [PATCH 533/708] Replace `IWorkingBeatmap` arg with `BeatmapVerifierContext` in checks This simplifies passing of contextual information by enabling addition without needing to refactor lots of classes. See next commit for example. --- .../Checks/CheckOffscreenObjectsTest.cs | 10 +++++-- .../Edit/Checks/CheckOffscreenObjects.cs | 3 +- .../Edit/OsuBeatmapVerifier.cs | 4 +-- .../Editing/Checks/CheckAudioQualityTest.cs | 28 ++++++++++------- .../Checks/CheckBackgroundQualityTest.cs | 30 +++++++++++-------- .../Editing/Checks/CheckFilePresenceTest.cs | 10 +++++-- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 4 +-- .../Rulesets/Edit/BeatmapVerifierContext.cs | 25 ++++++++++++++++ .../Rulesets/Edit/Checks/CheckAudioQuality.cs | 4 +-- .../Edit/Checks/CheckBackgroundQuality.cs | 6 ++-- .../Edit/Checks/CheckConcurrentObjects.cs | 2 +- .../Rulesets/Edit/Checks/CheckFilePresence.cs | 2 +- .../Edit/Checks/CheckUnsnappedObjects.cs | 2 +- .../Rulesets/Edit/Checks/Components/ICheck.cs | 4 +-- osu.Game/Rulesets/Edit/IBeatmapVerifier.cs | 2 +- osu.Game/Screens/Edit/Verify/IssueList.cs | 7 +++-- 16 files changed, 96 insertions(+), 47 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index 6139b0e676..5545273af2 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Checks; @@ -224,12 +225,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOk(IBeatmap beatmap) { - Assert.That(check.Run(beatmap, new TestWorkingBeatmap(beatmap)), Is.Empty); + var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); + Assert.That(check.Run(beatmap, context), Is.Empty); } private void assertOffscreenCircle(IBeatmap beatmap) { - var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); + var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); + var issues = check.Run(beatmap, context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle); @@ -237,7 +240,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOffscreenSlider(IBeatmap beatmap) { - var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); + var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); + var issues = check.Run(beatmap, context).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 4b0a7531a1..c25539201e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks new IssueTemplateOffscreenSlider(this) }; - public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) { foreach (var hitobject in playableBeatmap.HitObjects) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index dab6483179..18faeb4f9f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -17,9 +17,9 @@ namespace osu.Game.Rulesets.Osu.Edit new CheckOffscreenObjects() }; - public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) { - return checks.SelectMany(check => check.Run(playableBeatmap, workingBeatmap)); + return checks.SelectMany(check => check.Run(playableBeatmap, context)); } } } diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs index 7658ca728d..fbd02ea54e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs @@ -6,6 +6,7 @@ using Moq; using NUnit.Framework; using osu.Framework.Audio.Track; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; @@ -40,23 +41,23 @@ namespace osu.Game.Tests.Editing.Checks mock.SetupGet(w => w.Beatmap).Returns(beatmap); mock.SetupGet(w => w.Track).Returns((Track)null); - Assert.That(check.Run(beatmap, mock.Object), Is.Empty); + Assert.That(check.Run(beatmap, new BeatmapVerifierContext(mock.Object)), Is.Empty); } [Test] public void TestAcceptable() { - var mock = getMockWorkingBeatmap(192); + var context = getContext(192); - Assert.That(check.Run(beatmap, mock.Object), Is.Empty); + Assert.That(check.Run(beatmap, context), Is.Empty); } [Test] public void TestNullBitrate() { - var mock = getMockWorkingBeatmap(null); + var context = getContext(null); - var issues = check.Run(beatmap, mock.Object).ToList(); + var issues = check.Run(beatmap, context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate); @@ -65,9 +66,9 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestZeroBitrate() { - var mock = getMockWorkingBeatmap(0); + var context = getContext(0); - var issues = check.Run(beatmap, mock.Object).ToList(); + var issues = check.Run(beatmap, context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate); @@ -76,9 +77,9 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestTooHighBitrate() { - var mock = getMockWorkingBeatmap(320); + var context = getContext(320); - var issues = check.Run(beatmap, mock.Object).ToList(); + var issues = check.Run(beatmap, context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooHighBitrate); @@ -87,14 +88,19 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestTooLowBitrate() { - var mock = getMockWorkingBeatmap(64); + var context = getContext(64); - var issues = check.Run(beatmap, mock.Object).ToList(); + var issues = check.Run(beatmap, context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooLowBitrate); } + private BeatmapVerifierContext getContext(int? audioBitrate) + { + return new BeatmapVerifierContext(getMockWorkingBeatmap(audioBitrate).Object); + } + /// /// Returns the mock of the working beatmap with the given audio properties. /// diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index f0f972d2fa..e96ec5485d 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -9,6 +9,7 @@ using Moq; using NUnit.Framework; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; using FileInfo = osu.Game.IO.FileInfo; @@ -53,25 +54,25 @@ namespace osu.Game.Tests.Editing.Checks { // 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()); + var context = getContext(null, System.Array.Empty()); - Assert.That(check.Run(beatmap, mock.Object), Is.Empty); + Assert.That(check.Run(beatmap, context), Is.Empty); } [Test] public void TestAcceptable() { - var mock = getMockWorkingBeatmap(new Texture(1920, 1080)); + var context = getContext(new Texture(1920, 1080)); - Assert.That(check.Run(beatmap, mock.Object), Is.Empty); + Assert.That(check.Run(beatmap, context), Is.Empty); } [Test] public void TestTooHighResolution() { - var mock = getMockWorkingBeatmap(new Texture(3840, 2160)); + var context = getContext(new Texture(3840, 2160)); - var issues = check.Run(beatmap, mock.Object).ToList(); + var issues = check.Run(beatmap, context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooHighResolution); @@ -80,9 +81,9 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestLowResolution() { - var mock = getMockWorkingBeatmap(new Texture(640, 480)); + var context = getContext(new Texture(640, 480)); - var issues = check.Run(beatmap, mock.Object).ToList(); + var issues = check.Run(beatmap, context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateLowResolution); @@ -91,9 +92,9 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestTooLowResolution() { - var mock = getMockWorkingBeatmap(new Texture(100, 100)); + var context = getContext(new Texture(100, 100)); - var issues = check.Run(beatmap, mock.Object).ToList(); + var issues = check.Run(beatmap, context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooLowResolution); @@ -102,14 +103,19 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestTooUncompressed() { - var mock = getMockWorkingBeatmap(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); + var context = getContext(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); - var issues = check.Run(beatmap, mock.Object).ToList(); + var issues = check.Run(beatmap, context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed); } + private BeatmapVerifierContext getContext(Texture background, [CanBeNull] byte[] fileBytes = null) + { + return new BeatmapVerifierContext(getMockWorkingBeatmap(background, fileBytes).Object); + } + /// /// Returns the mock of the working beatmap with the given background and filesize. /// diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index f6e875a8fc..424dffcbc2 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.IO; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; using osu.Game.Tests.Beatmaps; @@ -45,7 +46,8 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestBackgroundSetAndInFiles() { - Assert.That(check.Run(beatmap, new TestWorkingBeatmap(beatmap)), Is.Empty); + var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); + Assert.That(check.Run(beatmap, context), Is.Empty); } [Test] @@ -53,7 +55,8 @@ namespace osu.Game.Tests.Editing.Checks { beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); - var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); + var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); + var issues = check.Run(beatmap, context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateDoesNotExist); @@ -64,7 +67,8 @@ namespace osu.Game.Tests.Editing.Checks { beatmap.Metadata.BackgroundFile = null; - var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); + var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); + var issues = check.Run(beatmap, context).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 2f7b7b0ab8..50902d3ff1 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -29,9 +29,9 @@ namespace osu.Game.Rulesets.Edit new CheckConcurrentObjects() }; - public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) { - return checks.SelectMany(check => check.Run(playableBeatmap, workingBeatmap)); + return checks.SelectMany(check => check.Run(playableBeatmap, context)); } } } diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs new file mode 100644 index 0000000000..86ca81491a --- /dev/null +++ b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Edit +{ + /// + /// Represents the context provided by the beatmap verifier to the checks it runs. + /// Contains information about what is being checked and how it should be checked. + /// + public class BeatmapVerifierContext + { + /// + /// The working beatmap instance of the current beatmap. + /// + public readonly IWorkingBeatmap WorkingBeatmap; + + public BeatmapVerifierContext(IWorkingBeatmap workingBeatmap) + { + WorkingBeatmap = workingBeatmap; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs index c1074d7c74..82e572f0b3 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs @@ -26,13 +26,13 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateNoBitrate(this) }; - public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) { var audioFile = playableBeatmap.Metadata?.AudioFile; if (audioFile == null) yield break; - var track = workingBeatmap.Track; + var track = context.WorkingBeatmap.Track; if (track?.Bitrate == null || track.Bitrate.Value == 0) yield return new IssueTemplateNoBitrate(this).Create(); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 59fee74023..2af1eca340 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -30,13 +30,13 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateTooUncompressed(this) }; - public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) { var backgroundFile = playableBeatmap.Metadata?.BackgroundFile; if (backgroundFile == null) yield break; - var texture = workingBeatmap.Background; + var texture = context.WorkingBeatmap.Background; if (texture == null) yield break; @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Edit.Checks yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); string storagePath = playableBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); - double filesizeMb = workingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); + double filesizeMb = context.WorkingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); if (filesizeMb > max_filesize_mb) yield return new IssueTemplateTooUncompressed(this).Create(filesizeMb); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs index ddebe2923a..d6e779d147 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateConcurrentDifferent(this) }; - public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) { for (int i = 0; i < playableBeatmap.HitObjects.Count - 1; ++i) { diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index 006fc57c04..cf990a2106 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(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) { var filename = GetFilename(playableBeatmap); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index cdf3f05465..f073915a39 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateSmallUnsnap(this) }; - public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) { var controlPointInfo = playableBeatmap.ControlPointInfo; diff --git a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs index 31a7583941..f669e2161c 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// Runs this check and returns any issues detected for the provided beatmap. /// /// 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); + /// The beatmap verifier context associated with the beatmap. + public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context); } } diff --git a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs index b598176a35..4e712351e0 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 playableBeatmap, WorkingBeatmap workingBeatmap); + public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context); } } diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 3e836c4010..96d056fba4 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -40,6 +40,7 @@ namespace osu.Game.Screens.Edit.Verify private IBeatmapVerifier rulesetVerifier; private BeatmapVerifier generalVerifier; + private BeatmapVerifierContext context; [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) @@ -57,6 +58,8 @@ namespace osu.Game.Screens.Edit.Verify InterpretedDifficulty = new Bindable(beatmap.BeatmapInfo.DifficultyRating); + context = new BeatmapVerifierContext(workingBeatmap.Value); + RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] @@ -101,10 +104,10 @@ namespace osu.Game.Screens.Edit.Verify public void Refresh() { - var issues = generalVerifier.Run(beatmap, workingBeatmap.Value); + var issues = generalVerifier.Run(beatmap, context); if (rulesetVerifier != null) - issues = issues.Concat(rulesetVerifier.Run(beatmap, workingBeatmap.Value)); + issues = issues.Concat(rulesetVerifier.Run(beatmap, context)); issues = filter(issues); From 64d96b06a66c9f35026b925573b9f3fc7ebfb72d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 02:30:21 +0200 Subject: [PATCH 534/708] Add interpreted difficulty info to `BeatmapVerifierContext` Enables checks to make use of the difficulty level as shown in the settings UI. --- osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs | 8 +++++++- osu.Game/Screens/Edit/Verify/IssueList.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs index 86ca81491a..59d43ba3d6 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs @@ -17,9 +17,15 @@ namespace osu.Game.Rulesets.Edit /// public readonly IWorkingBeatmap WorkingBeatmap; - public BeatmapVerifierContext(IWorkingBeatmap workingBeatmap) + /// + /// The difficulty level which the current beatmap is considered to be. + /// + public readonly Bindable InterpretedDifficulty; + + public BeatmapVerifierContext(IWorkingBeatmap workingBeatmap, DifficultyRating difficultyRating = DifficultyRating.ExpertPlus) { WorkingBeatmap = workingBeatmap; + InterpretedDifficulty = new Bindable(difficultyRating); } } } diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 96d056fba4..2ae86ed198 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -59,6 +59,7 @@ namespace osu.Game.Screens.Edit.Verify InterpretedDifficulty = new Bindable(beatmap.BeatmapInfo.DifficultyRating); context = new BeatmapVerifierContext(workingBeatmap.Value); + context.InterpretedDifficulty.BindTo(InterpretedDifficulty); RelativeSizeAxes = Axes.Both; From b7bc42e0d37db4f5d5bfc201d70e036e25bf9ce7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 12 May 2021 02:34:16 +0200 Subject: [PATCH 535/708] Rename "playableBeatmap" check arg to "beatmap" The working beatmap is now in the context, so it's easier to distinguish beatmap type, hence no need for this prefix. --- .../Edit/Checks/CheckOffscreenObjects.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs | 4 ++-- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 4 ++-- osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs | 4 ++-- .../Rulesets/Edit/Checks/CheckBackgroundPresence.cs | 2 +- .../Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 6 +++--- .../Rulesets/Edit/Checks/CheckConcurrentObjects.cs | 10 +++++----- osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs | 8 ++++---- osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs | 6 +++--- osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs | 4 ++-- osu.Game/Rulesets/Edit/IBeatmapVerifier.cs | 2 +- 12 files changed, 28 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index c25539201e..86bb7f203f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -32,9 +32,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks new IssueTemplateOffscreenSlider(this) }; - public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) + public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) { - foreach (var hitobject in playableBeatmap.HitObjects) + foreach (var hitobject in beatmap.HitObjects) { switch (hitobject) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 18faeb4f9f..c62f472d75 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -17,9 +17,9 @@ namespace osu.Game.Rulesets.Osu.Edit new CheckOffscreenObjects() }; - public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) + public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) { - return checks.SelectMany(check => check.Run(playableBeatmap, context)); + return checks.SelectMany(check => check.Run(beatmap, context)); } } } diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 50902d3ff1..1860d54b57 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -29,9 +29,9 @@ namespace osu.Game.Rulesets.Edit new CheckConcurrentObjects() }; - public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) + public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) { - return checks.SelectMany(check => check.Run(playableBeatmap, context)); + return checks.SelectMany(check => check.Run(beatmap, context)); } } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs index 2d572a521e..94c48c300a 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(IBeatmap playableBeatmap) => playableBeatmap.Metadata?.AudioFile; + protected override string GetFilename(IBeatmap beatmap) => beatmap.Metadata?.AudioFile; } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs index 82e572f0b3..1015f267aa 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs @@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateNoBitrate(this) }; - public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) + public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) { - var audioFile = playableBeatmap.Metadata?.AudioFile; + var audioFile = beatmap.Metadata?.AudioFile; if (audioFile == null) yield break; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs index 233c708a25..067800b409 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(IBeatmap playableBeatmap) => playableBeatmap.Metadata?.BackgroundFile; + protected override string GetFilename(IBeatmap beatmap) => beatmap.Metadata?.BackgroundFile; } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 2af1eca340..87f5c80c89 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(IBeatmap playableBeatmap, BeatmapVerifierContext context) + public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) { - var backgroundFile = playableBeatmap.Metadata?.BackgroundFile; + var backgroundFile = beatmap.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 = playableBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); + string storagePath = beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); double filesizeMb = context.WorkingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); if (filesizeMb > max_filesize_mb) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs index d6e779d147..fd6ed664e6 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -22,15 +22,15 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateConcurrentDifferent(this) }; - public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) + public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) { - for (int i = 0; i < playableBeatmap.HitObjects.Count - 1; ++i) + for (int i = 0; i < beatmap.HitObjects.Count - 1; ++i) { - var hitobject = playableBeatmap.HitObjects[i]; + var hitobject = beatmap.HitObjects[i]; - for (int j = i + 1; j < playableBeatmap.HitObjects.Count; ++j) + for (int j = i + 1; j < beatmap.HitObjects.Count; ++j) { - var nextHitobject = playableBeatmap.HitObjects[j]; + var nextHitobject = beatmap.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. diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index cf990a2106..f04909d175 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(IBeatmap playableBeatmap); + protected abstract string GetFilename(IBeatmap beatmap); public CheckMetadata Metadata => new CheckMetadata(Category, $"Missing {TypeOfFile}"); @@ -21,9 +21,9 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateDoesNotExist(this) }; - public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) + public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) { - var filename = GetFilename(playableBeatmap); + var filename = GetFilename(beatmap); if (filename == null) { @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Edit.Checks } // If the file is set, also make sure it still exists. - var storagePath = playableBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); + var storagePath = beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); if (storagePath != null) yield break; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index f073915a39..aa19f3df07 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateSmallUnsnap(this) }; - public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context) + public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) { - var controlPointInfo = playableBeatmap.ControlPointInfo; + var controlPointInfo = beatmap.ControlPointInfo; - foreach (var hitobject in playableBeatmap.HitObjects) + foreach (var hitobject in beatmap.HitObjects) { double startUnsnap = hitobject.StartTime - controlPointInfo.GetClosestSnappedTime(hitobject.StartTime); string startPostfix = hitobject is IHasDuration ? "start" : ""; diff --git a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs index f669e2161c..511d6aaa0f 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs @@ -24,8 +24,8 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// Runs this check and returns any issues detected for the provided beatmap. /// - /// The playable beatmap of the beatmap to run the check on. + /// The playable beatmap of the beatmap to run the check on. /// The beatmap verifier context associated with the beatmap. - public IEnumerable Run(IBeatmap playableBeatmap, BeatmapVerifierContext context); + public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context); } } diff --git a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs index 4e712351e0..1dafc6938e 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 playableBeatmap, BeatmapVerifierContext context); + public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context); } } From 9b09361cc9a5b3ef1fb9cecf34e28f6401ae9def Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 12:16:41 +0900 Subject: [PATCH 536/708] Add testable spectator streaming client --- .../Spectator/TestSpectatorStreamingClient.cs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs new file mode 100644 index 0000000000..c177d3f399 --- /dev/null +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs @@ -0,0 +1,84 @@ +// 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.Utils; +using osu.Game.Online; +using osu.Game.Online.Spectator; +using osu.Game.Replays.Legacy; +using osu.Game.Scoring; + +namespace osu.Game.Tests.Visual.Spectator +{ + public class TestSpectatorStreamingClient : SpectatorStreamingClient + { + public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; + private readonly HashSet watchingUsers = new HashSet(); + + private readonly Dictionary userBeatmapDictionary = new Dictionary(); + private readonly Dictionary userSentStateDictionary = new Dictionary(); + + public TestSpectatorStreamingClient() + : base(new DevelopmentEndpointConfiguration()) + { + } + + public void StartPlay(int userId, int beatmapId) + { + userBeatmapDictionary[userId] = beatmapId; + sendState(userId, beatmapId); + } + + public void EndPlay(int userId, int beatmapId) + { + ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState + { + BeatmapID = beatmapId, + RulesetID = 0, + }); + + userSentStateDictionary[userId] = false; + } + + public void SendFrames(int userId, int index, int count) + { + var frames = new List(); + + for (int i = index; i < index + count; i++) + { + var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1; + + frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); + } + + var bundle = new FrameDataBundle(new ScoreInfo(), frames); + ((ISpectatorClient)this).UserSentFrames(userId, bundle); + + if (!userSentStateDictionary[userId]) + sendState(userId, userBeatmapDictionary[userId]); + } + + public override void WatchUser(int userId) + { + // When newly watching a user, the server sends the playing state immediately. + if (!watchingUsers.Contains(userId) && PlayingUsers.Contains(userId)) + sendState(userId, userBeatmapDictionary[userId]); + + base.WatchUser(userId); + + watchingUsers.Add(userId); + } + + private void sendState(int userId, int beatmapId) + { + ((ISpectatorClient)this).UserBeganPlaying(userId, new SpectatorState + { + BeatmapID = beatmapId, + RulesetID = 0, + }); + + userSentStateDictionary[userId] = true; + } + } +} From 184dbaf2029a4b123213b068c0c19484770e57d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 12:53:30 +0900 Subject: [PATCH 537/708] Improve safety of bindings in `HealthDisplay` --- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 23 ++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index e0ddde026b..d6b4934731 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD /// public abstract class HealthDisplay : Container { - private Bindable showHealthbar; + private readonly Bindable showHealthbar = new Bindable(true); [Resolved] protected HealthProcessor HealthProcessor { get; private set; } @@ -32,18 +32,21 @@ namespace osu.Game.Screens.Play.HUD { } - [BackgroundDependencyLoader(true)] - private void load(HUDOverlay hud) - { - Current.BindTo(HealthProcessor.Health); + [Resolved(canBeNull: true)] + private HUDOverlay hudOverlay { get; set; } + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindTo(HealthProcessor.Health); HealthProcessor.NewJudgement += onNewJudgement; - if (hud != null) - { - showHealthbar = hud.ShowHealthbar.GetBoundCopy(); - showHealthbar.BindValueChanged(healthBar => this.FadeTo(healthBar.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true); - } + if (hudOverlay != null) + showHealthbar.BindTo(hudOverlay.ShowHealthbar); + + // this probably shouldn't be operating on `this.` + showHealthbar.BindValueChanged(healthBar => this.FadeTo(healthBar.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true); } private void onNewJudgement(JudgementResult judgement) From bf44c09a9142d567878a27e2a30fc1b311201023 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 12:59:46 +0900 Subject: [PATCH 538/708] Add name identifying container and rename index variable --- osu.Game.Rulesets.Mania/UI/Column.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 88c3bd5dbf..827d8e2a0a 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -73,6 +73,7 @@ namespace osu.Game.Rulesets.Mania.UI background, new Container { + Name = "Column samples pool", RelativeSizeAxes = Axes.Both, ChildrenEnumerable = hitSounds = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray() }, @@ -132,7 +133,7 @@ namespace osu.Game.Rulesets.Mania.UI HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); } - private int nextHitSound; + private int nextHitSoundIndex; public bool OnPressed(ManiaAction action) { @@ -147,12 +148,12 @@ namespace osu.Game.Rulesets.Mania.UI if (nextObject is DrawableManiaHitObject maniaObject) { - var hitSound = hitSounds[nextHitSound]; + var hitSound = hitSounds[nextHitSoundIndex]; hitSound.Samples = maniaObject.GetGameplaySamples(); hitSound.Play(); - nextHitSound = (nextHitSound + 1) % max_concurrent_hitsounds; + nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds; } return true; From 3428056113d4c8a2cdfd391af9c9e81d32d42241 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 13:00:02 +0900 Subject: [PATCH 539/708] Remove unnecessary usage of `ChildrenEnumerable` for array assignment --- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 827d8e2a0a..79a41ae19e 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Mania.UI { Name = "Column samples pool", RelativeSizeAxes = Axes.Both, - ChildrenEnumerable = hitSounds = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray() + Children = hitSounds = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray() }, TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } }; From 21fc0ba43baea6e0cb115952ab21df06f1d1c003 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 12:17:42 +0900 Subject: [PATCH 540/708] Combine test spectator streaming client implementations --- .../Visual/Gameplay/TestSceneSpectator.cs | 89 ++----------------- .../TestSceneMultiSpectatorLeaderboard.cs | 70 +-------------- .../TestSceneMultiSpectatorScreen.cs | 75 +--------------- .../TestSceneCurrentlyPlayingDisplay.cs | 4 +- 4 files changed, 11 insertions(+), 227 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 0777571d02..a7ed217b4d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -1,35 +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.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Online; using osu.Game.Online.Spectator; -using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; -using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Visual.Multiplayer; +using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSpectator : ScreenTestScene { + private readonly User streamingUser = new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Test user" }; + [Cached(typeof(SpectatorStreamingClient))] private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient(); @@ -215,9 +212,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void waitForPlayer() => AddUntilStep("wait for player", () => Stack.CurrentScreen is Player); - private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorStreamingClient.StartPlay(beatmapId ?? importedBeatmapId)); + private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorStreamingClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); - private void finish(int? beatmapId = null) => AddStep("end play", () => testSpectatorStreamingClient.EndPlay(beatmapId ?? importedBeatmapId)); + private void finish(int? beatmapId = null) => AddStep("end play", () => testSpectatorStreamingClient.EndPlay(streamingUser.Id, beatmapId ?? importedBeatmapId)); private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); @@ -226,89 +223,17 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("send frames", () => { - testSpectatorStreamingClient.SendFrames(nextFrame, count); + testSpectatorStreamingClient.SendFrames(streamingUser.Id, nextFrame, count); nextFrame += count; }); } private void loadSpectatingScreen() { - AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(testSpectatorStreamingClient.StreamingUser))); + AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser))); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded); } - public class TestSpectatorStreamingClient : SpectatorStreamingClient - { - public readonly User StreamingUser = new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Test user" }; - - public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; - - private int beatmapId; - - public TestSpectatorStreamingClient() - : base(new DevelopmentEndpointConfiguration()) - { - } - - public void StartPlay(int beatmapId) - { - this.beatmapId = beatmapId; - sendState(beatmapId); - } - - public void EndPlay(int beatmapId) - { - ((ISpectatorClient)this).UserFinishedPlaying(StreamingUser.Id, new SpectatorState - { - BeatmapID = beatmapId, - RulesetID = 0, - }); - - sentState = false; - } - - private bool sentState; - - public void SendFrames(int index, int count) - { - var frames = new List(); - - for (int i = index; i < index + count; i++) - { - var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1; - - frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); - } - - var bundle = new FrameDataBundle(new ScoreInfo(), frames); - ((ISpectatorClient)this).UserSentFrames(StreamingUser.Id, bundle); - - if (!sentState) - sendState(beatmapId); - } - - public override void WatchUser(int userId) - { - if (!PlayingUsers.Contains(userId) && sentState) - { - // usually the server would do this. - sendState(beatmapId); - } - - base.WatchUser(userId); - } - - private void sendState(int beatmapId) - { - sentState = true; - ((ISpectatorClient)this).UserBeganPlaying(StreamingUser.Id, new SpectatorState - { - BeatmapID = beatmapId, - RulesetID = 0, - }); - } - } - internal class TestUserLookupCache : UserLookupCache { protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index d3a5daeac6..263adc07e1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -11,15 +11,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Framework.Utils; using osu.Game.Database; -using osu.Game.Online; using osu.Game.Online.Spectator; -using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play.HUD; +using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer @@ -149,71 +146,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); - private class TestSpectatorStreamingClient : SpectatorStreamingClient - { - private readonly Dictionary userBeatmapDictionary = new Dictionary(); - private readonly Dictionary userSentStateDictionary = new Dictionary(); - - public TestSpectatorStreamingClient() - : base(new DevelopmentEndpointConfiguration()) - { - } - - public void StartPlay(int userId, int beatmapId) - { - userBeatmapDictionary[userId] = beatmapId; - userSentStateDictionary[userId] = false; - sendState(userId, beatmapId); - } - - public void EndPlay(int userId, int beatmapId) - { - ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState - { - BeatmapID = beatmapId, - RulesetID = 0, - }); - userSentStateDictionary[userId] = false; - } - - public void SendFrames(int userId, int index, int count) - { - var frames = new List(); - - for (int i = index; i < index + count; i++) - { - var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1; - frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); - } - - var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames); - ((ISpectatorClient)this).UserSentFrames(userId, bundle); - if (!userSentStateDictionary[userId]) - sendState(userId, userBeatmapDictionary[userId]); - } - - public override void WatchUser(int userId) - { - if (userSentStateDictionary[userId]) - { - // usually the server would do this. - sendState(userId, userBeatmapDictionary[userId]); - } - - base.WatchUser(userId); - } - - private void sendState(int userId, int beatmapId) - { - ((ISpectatorClient)this).UserBeganPlaying(userId, new SpectatorState - { - BeatmapID = beatmapId, - RulesetID = 0, - }); - userSentStateDictionary[userId] = true; - } - } - private class TestUserLookupCache : UserLookupCache { protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index db431c0a82..689c249d05 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -9,16 +9,13 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Online; using osu.Game.Online.Spectator; -using osu.Game.Replays.Legacy; -using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer @@ -301,76 +298,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); - public class TestSpectatorStreamingClient : SpectatorStreamingClient - { - private readonly Dictionary userBeatmapDictionary = new Dictionary(); - private readonly Dictionary userSentStateDictionary = new Dictionary(); - - public TestSpectatorStreamingClient() - : base(new DevelopmentEndpointConfiguration()) - { - } - - public void StartPlay(int userId, int beatmapId) - { - userBeatmapDictionary[userId] = beatmapId; - userSentStateDictionary[userId] = false; - - sendState(userId, beatmapId); - } - - public void EndPlay(int userId, int beatmapId) - { - ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState - { - BeatmapID = beatmapId, - RulesetID = 0, - }); - - userSentStateDictionary[userId] = false; - } - - public void SendFrames(int userId, int index, int count) - { - var frames = new List(); - - for (int i = index; i < index + count; i++) - { - var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1; - - frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); - } - - var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames); - ((ISpectatorClient)this).UserSentFrames(userId, bundle); - - if (!userSentStateDictionary[userId]) - sendState(userId, userBeatmapDictionary[userId]); - } - - public override void WatchUser(int userId) - { - if (!PlayingUsers.Contains(userId) && userSentStateDictionary.TryGetValue(userId, out var sent) && sent) - { - // usually the server would do this. - sendState(userId, userBeatmapDictionary[userId]); - } - - base.WatchUser(userId); - } - - private void sendState(int userId, int beatmapId) - { - ((ISpectatorClient)this).UserBeganPlaying(userId, new SpectatorState - { - BeatmapID = beatmapId, - RulesetID = 0, - }); - - userSentStateDictionary[userId] = true; - } - } - internal class TestUserLookupCache : UserLookupCache { protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs index 1baa07f208..8ae6398003 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -12,7 +12,7 @@ using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Online.Spectator; using osu.Game.Overlays.Dashboard; -using osu.Game.Tests.Visual.Gameplay; +using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Online public class TestSceneCurrentlyPlayingDisplay : OsuTestScene { [Cached(typeof(SpectatorStreamingClient))] - private TestSceneSpectator.TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSceneSpectator.TestSpectatorStreamingClient(); + private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient(); private CurrentlyPlayingDisplay currentlyPlaying; From ad11818868d50e704d98ec28ec12f7ee2a2df9b8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 12:19:36 +0900 Subject: [PATCH 541/708] Remove watched users on stop watching --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 2 +- .../Visual/Spectator/TestSpectatorStreamingClient.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 378096c7fb..691ad5624a 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -230,7 +230,7 @@ namespace osu.Game.Online.Spectator connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); } - public void StopWatchingUser(int userId) + public virtual void StopWatchingUser(int userId) { lock (userLock) { diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs index c177d3f399..806d9b45ab 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs @@ -38,7 +38,8 @@ namespace osu.Game.Tests.Visual.Spectator RulesetID = 0, }); - userSentStateDictionary[userId] = false; + userBeatmapDictionary.Remove(userId); + userSentStateDictionary.Remove(userId); } public void SendFrames(int userId, int index, int count) @@ -66,10 +67,15 @@ namespace osu.Game.Tests.Visual.Spectator sendState(userId, userBeatmapDictionary[userId]); base.WatchUser(userId); - watchingUsers.Add(userId); } + public override void StopWatchingUser(int userId) + { + base.StopWatchingUser(userId); + watchingUsers.Remove(userId); + } + private void sendState(int userId, int beatmapId) { ((ISpectatorClient)this).UserBeganPlaying(userId, new SpectatorState From e0e8f5ab80700db4113b5df45a7d91046cbe57ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 13:06:28 +0900 Subject: [PATCH 542/708] Fix ordering + threading issues --- .../Spectator/TestSpectatorStreamingClient.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs index 806d9b45ab..c2428ce415 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.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.Concurrent; using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Utils; @@ -14,7 +15,7 @@ namespace osu.Game.Tests.Visual.Spectator public class TestSpectatorStreamingClient : SpectatorStreamingClient { public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; - private readonly HashSet watchingUsers = new HashSet(); + private readonly ConcurrentDictionary watchingUsers = new ConcurrentDictionary(); private readonly Dictionary userBeatmapDictionary = new Dictionary(); private readonly Dictionary userSentStateDictionary = new Dictionary(); @@ -62,18 +63,17 @@ namespace osu.Game.Tests.Visual.Spectator public override void WatchUser(int userId) { - // When newly watching a user, the server sends the playing state immediately. - if (!watchingUsers.Contains(userId) && PlayingUsers.Contains(userId)) - sendState(userId, userBeatmapDictionary[userId]); - base.WatchUser(userId); - watchingUsers.Add(userId); + + // When newly watching a user, the server sends the playing state immediately. + if (watchingUsers.TryAdd(userId, 0) && PlayingUsers.Contains(userId)) + sendState(userId, userBeatmapDictionary[userId]); } public override void StopWatchingUser(int userId) { base.StopWatchingUser(userId); - watchingUsers.Remove(userId); + watchingUsers.TryRemove(userId, out _); } private void sendState(int userId, int beatmapId) From f4c96b26750a9c1556a7c62b5f60e95b1a95e476 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 13:10:59 +0900 Subject: [PATCH 543/708] Only update playing user states when users are watched --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 691ad5624a..ec6d1bf9d8 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -142,7 +142,11 @@ namespace osu.Game.Online.Spectator if (!playingUsers.Contains(userId)) playingUsers.Add(userId); - playingUserStates[userId] = state; + // UserBeganPlaying() is called by the server regardless of whether the local user is watching the remote user, and is called a further time when the remote user is watched. + // This may be a temporary thing (see: https://github.com/ppy/osu-server-spectator/blob/2273778e02cfdb4a9c6a934f2a46a8459cb5d29c/osu.Server.Spectator/Hubs/SpectatorHub.cs#L28-L29). + // We don't want the user states to update unless the player is being watched, otherwise calling BindUserBeganPlaying() can lead to double invocations. + if (watchingUsers.Contains(userId)) + playingUserStates[userId] = state; } OnUserBeganPlaying?.Invoke(userId, state); From 672108edcf841b30d3f40a8bdb58695b206272c9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 13:27:12 +0900 Subject: [PATCH 544/708] Use container instead of array for field --- osu.Game.Rulesets.Mania/UI/Column.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 79a41ae19e..0f02e2cd4b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.UI internal readonly Container TopLevelContainer; private readonly DrawablePool hitExplosionPool; private readonly OrderedHitPolicy hitPolicy; - private readonly SkinnableSound[] hitSounds; + private readonly Container hitSounds; public Container UnderlayElements => HitObjectArea.UnderlayElements; @@ -71,11 +71,11 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both }, background, - new Container + hitSounds = new Container { Name = "Column samples pool", RelativeSizeAxes = Axes.Both, - Children = hitSounds = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray() + Children = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray() }, TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } }; From 05c21fb5b39ba1f0b52db2df3f06aacee25f4e99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 13:27:30 +0900 Subject: [PATCH 545/708] Increase mania HP lenience --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b3889bc7d3..fbb9b3c466 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); - public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.2); + public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.5); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); From 1d383024e226f72001902bb3c5587e472aedec9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 13:51:29 +0900 Subject: [PATCH 546/708] Improve the visual appearance of skin editor blueprints --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 67 ++++++++++++++++++++--- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index b8dfdbad0a..23411f2b3d 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -2,14 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; 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.Graphics.Sprites; using osu.Game.Rulesets.Edit; using osuTK; +using osuTK.Graphics; namespace osu.Game.Skinning.Editor { @@ -17,14 +19,19 @@ namespace osu.Game.Skinning.Editor { private Container box; + private Container outlineBox; + private Drawable drawable => (Drawable)Item; /// /// Whether the blueprint should be shown even when the is not alive. /// - protected virtual bool AlwaysShowWhenSelected => false; + protected virtual bool AlwaysShowWhenSelected => true; - protected override bool ShouldBeAlive => (drawable.IsAlive && Item.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); + protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent; + + [Resolved] + private OsuColour colours { get; set; } public SkinBlueprint(ISkinnableComponent component) : base(component) @@ -32,26 +39,68 @@ namespace osu.Game.Skinning.Editor } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChildren = new Drawable[] { box = new Container { - Colour = colours.Yellow, Children = new Drawable[] { - new Box + outlineBox = new Container { RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - AlwaysPresent = true, + Masking = true, + BorderThickness = 3, + BorderColour = Color4.White, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0f, + AlwaysPresent = true, + }, + } }, - } + new OsuSpriteText + { + Text = Item.GetType().Name, + Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold), + Anchor = Anchor.BottomRight, + Origin = Anchor.TopRight, + } + }, }, }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + updateSelectedState(); + box.FadeInFromZero(200, Easing.OutQuint); + } + + protected override void OnSelected() + { + // base logic hides selected blueprints when not selected, but timeline doesn't do that. + updateSelectedState(); + } + + protected override void OnDeselected() + { + // base logic hides selected blueprints when not selected, but timeline doesn't do that. + updateSelectedState(); + } + + private void updateSelectedState() + { + outlineBox.FadeColour(colours.Pink.Opacity(IsSelected ? 1 : 0.5f), 200, Easing.OutQuint); + outlineBox.Child.FadeTo(IsSelected ? 0.2f : 0, 200, Easing.OutQuint); + } + private Quad drawableQuad; public override Quad ScreenSpaceDrawQuad => drawableQuad; From 96d4011de2927520121581413837f34e78657abe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 14:02:20 +0900 Subject: [PATCH 547/708] Use pattern matching to tidy up instance construction --- osu.Game/Skinning/Editor/SkinEditor.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 08fc458b94..b32d117a92 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -166,15 +166,13 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - var instance = (Drawable)Activator.CreateInstance(type) as ISkinnableComponent; - - if (instance == null) + if (!(Activator.CreateInstance(type) is ISkinnableComponent component)) throw new InvalidOperationException("Attempted to instantiate a component for placement which was not an {typeof(ISkinnableComponent)}."); - getTarget(SkinnableTarget.MainHUDComponents)?.Add(instance); + getTarget(SkinnableTarget.MainHUDComponents)?.Add(component); SelectedComponents.Clear(); - SelectedComponents.Add(instance); + SelectedComponents.Add(component); } private ISkinnableTarget getTarget(SkinnableTarget target) From 42e67952517e43eef66f688099bd5490efe6ea36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 14:11:40 +0900 Subject: [PATCH 548/708] Place new skin components at the centre of the screen by default --- osu.Game/Skinning/Editor/SkinEditor.cs | 14 +++++++++++++- osu.Game/Skinning/ISkinnableTarget.cs | 4 +++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index b32d117a92..4e38caa806 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -166,10 +166,22 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { + var targetContainer = getTarget(SkinnableTarget.MainHUDComponents); + + if (targetContainer == null) + return; + if (!(Activator.CreateInstance(type) is ISkinnableComponent component)) throw new InvalidOperationException("Attempted to instantiate a component for placement which was not an {typeof(ISkinnableComponent)}."); - getTarget(SkinnableTarget.MainHUDComponents)?.Add(component); + var drawableComponent = (Drawable)component; + + // give newly added components a sane starting location. + drawableComponent.Origin = Anchor.TopCentre; + drawableComponent.Anchor = Anchor.TopCentre; + drawableComponent.Y = targetContainer.DrawSize.Y / 2; + + targetContainer.Add(component); SelectedComponents.Clear(); SelectedComponents.Add(component); diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 4d97b42e8e..4f6e3e66c3 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -1,12 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; + namespace osu.Game.Skinning { /// /// Denotes a container which can house s. /// - public interface ISkinnableTarget + public interface ISkinnableTarget : IDrawable { public SkinnableTarget Target { get; } From 273cd18b8aac5d2b8593dac1e48bdd609957e7e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 14:19:36 +0900 Subject: [PATCH 549/708] Use test streaming client in gameplay leaderboard test --- ...TestSceneMultiplayerGameplayLeaderboard.cs | 32 +++---------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index b6c06bb149..6813a6e7dd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -6,14 +6,12 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; @@ -22,6 +20,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Tests.Visual.Online; +using osu.Game.Tests.Visual.Spectator; namespace osu.Game.Tests.Visual.Multiplayer { @@ -30,7 +29,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private const int users = 16; [Cached(typeof(SpectatorStreamingClient))] - private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(users); + private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(); [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); @@ -71,7 +70,8 @@ namespace osu.Game.Tests.Visual.Multiplayer var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); - streamingClient.Start(Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + for (int i = 0; i < users; i++) + streamingClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); Client.CurrentMatchPlayingUserIds.Clear(); Client.CurrentMatchPlayingUserIds.AddRange(streamingClient.PlayingUsers); @@ -114,30 +114,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); } - public class TestMultiplayerStreaming : SpectatorStreamingClient + public class TestMultiplayerStreaming : TestSpectatorStreamingClient { - public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; - - private readonly int totalUsers; - - public TestMultiplayerStreaming(int totalUsers) - : base(new DevelopmentEndpointConfiguration()) - { - this.totalUsers = totalUsers; - } - - public void Start(int beatmapId) - { - for (int i = 0; i < totalUsers; i++) - { - ((ISpectatorClient)this).UserBeganPlaying(i, new SpectatorState - { - BeatmapID = beatmapId, - RulesetID = 0, - }); - } - } - private readonly Dictionary lastHeaders = new Dictionary(); public void RandomlyUpdateState() From e1dacde31456e2366226c64398b0dab1a116d3ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 14:22:15 +0900 Subject: [PATCH 550/708] Add combo to test streaming client --- osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs index c2428ce415..cc8437479d 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorStreamingClient.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Spectator frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); } - var bundle = new FrameDataBundle(new ScoreInfo(), frames); + var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames); ((ISpectatorClient)this).UserSentFrames(userId, bundle); if (!userSentStateDictionary[userId]) From d55f42dc2eed23c66208bd906386c09ff0a8dcb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 15:05:53 +0900 Subject: [PATCH 551/708] Show anchor and origin in skin blueprints when selected --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 64 ++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index 23411f2b3d..7db5bc7ead 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.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.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -21,6 +22,8 @@ namespace osu.Game.Skinning.Editor private Container outlineBox; + private AnchorOriginVisualiser anchorOriginVisualiser; + private Drawable drawable => (Drawable)Item; /// @@ -69,9 +72,13 @@ namespace osu.Game.Skinning.Editor Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold), Anchor = Anchor.BottomRight, Origin = Anchor.TopRight, - } + }, }, }, + anchorOriginVisualiser = new AnchorOriginVisualiser(drawable) + { + Alpha = 0, + } }; } @@ -80,7 +87,7 @@ namespace osu.Game.Skinning.Editor base.LoadComplete(); updateSelectedState(); - box.FadeInFromZero(200, Easing.OutQuint); + this.FadeInFromZero(200, Easing.OutQuint); } protected override void OnSelected() @@ -99,6 +106,8 @@ namespace osu.Game.Skinning.Editor { outlineBox.FadeColour(colours.Pink.Opacity(IsSelected ? 1 : 0.5f), 200, Easing.OutQuint); outlineBox.Child.FadeTo(IsSelected ? 0.2f : 0, 200, Easing.OutQuint); + + anchorOriginVisualiser.FadeTo(IsSelected ? 1 : 0, 200, Easing.OutQuint); } private Quad drawableQuad; @@ -123,4 +132,55 @@ namespace osu.Game.Skinning.Editor public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; } + + internal class AnchorOriginVisualiser : CompositeDrawable + { + private readonly Drawable drawable; + + private readonly Box originBox; + + private readonly Box anchorBox; + private readonly Box anchorLine; + + public AnchorOriginVisualiser(Drawable drawable) + { + this.drawable = drawable; + + InternalChildren = new Drawable[] + { + anchorLine = new Box + { + Colour = Color4.Yellow, + Height = 2, + }, + originBox = new Box + { + Colour = Color4.Red, + Origin = Anchor.Centre, + Size = new Vector2(5), + }, + anchorBox = new Box + { + Colour = Color4.Red, + Origin = Anchor.Centre, + Size = new Vector2(5), + }, + }; + } + + protected override void Update() + { + base.Update(); + + originBox.Position = drawable.ToSpaceOfOtherDrawable(drawable.OriginPosition, this); + anchorBox.Position = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this); + + var point1 = ToLocalSpace(anchorBox.ScreenSpaceDrawQuad.Centre); + var point2 = ToLocalSpace(originBox.ScreenSpaceDrawQuad.Centre); + + anchorLine.Position = point1; + anchorLine.Width = (point2 - point1).Length; + anchorLine.Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)); + } + } } From 05e0c57a6af3fd579abc9ea73fd4de33702f9284 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 15:30:52 +0900 Subject: [PATCH 552/708] Keep component positions stable when changing anchor/origin --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index cf5ece03e9..410ec7a272 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -109,13 +109,25 @@ namespace osu.Game.Skinning.Editor private void applyOrigin(Anchor anchor) { foreach (var item in SelectedItems) - ((Drawable)item).Origin = anchor; + { + var drawable = (Drawable)item; + + var previousOrigin = drawable.OriginPosition; + drawable.Origin = anchor; + drawable.Position += drawable.OriginPosition - previousOrigin; + } } private void applyAnchor(Anchor anchor) { foreach (var item in SelectedItems) - ((Drawable)item).Anchor = anchor; + { + var drawable = (Drawable)item; + + var previousAnchor = (drawable.AnchorPosition); + drawable.Anchor = anchor; + drawable.Position -= drawable.AnchorPosition - previousAnchor; + } } private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) From 29e6f6b6b6cc1cc8931c13525da01f224ae8de89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 15:58:21 +0900 Subject: [PATCH 553/708] Remove `public` prefixes from interface type and add `Components` list for future use --- .../Skinning/Editor/SkinBlueprintContainer.cs | 3 +-- osu.Game/Skinning/ISkinnableTarget.cs | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index ef189ed165..9890b8e8b6 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -12,7 +12,6 @@ using osu.Framework.Testing; using osu.Game.Rulesets.Edit; using osu.Game.Screens; using osu.Game.Screens.Edit.Compose.Components; -using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning.Editor { @@ -38,7 +37,7 @@ namespace osu.Game.Skinning.Editor base.LoadComplete(); // track each target container on the current screen. - var targetContainers = target.ChildrenOfType().ToArray(); + var targetContainers = target.ChildrenOfType().ToArray(); if (targetContainers.Length == 0) { diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 4d97b42e8e..ab3c24a1e2 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.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 osu.Framework.Bindables; + namespace osu.Game.Skinning { /// @@ -8,16 +10,24 @@ namespace osu.Game.Skinning /// public interface ISkinnableTarget { - public SkinnableTarget Target { get; } + /// + /// The definition of this target. + /// + SkinnableTarget Target { get; } + + /// + /// A bindable list of components which are being tracked by this skinnable target. + /// + IBindableList Components { get; } /// /// Reload this target from the current skin. /// - public void Reload(); + void Reload(); /// /// Add the provided item to this target. /// - public void Add(ISkinnableComponent drawable); + void Add(ISkinnableComponent drawable); } } From 494a1b01a536d6470b2700d4879cd0a05dc13e7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 15:59:33 +0900 Subject: [PATCH 554/708] Move `SkinnableElementTargetContainer` out of HUD namespace --- osu.Game/Skinning/Editor/SkinEditor.cs | 1 - .../Play/HUD => Skinning}/SkinnableElementTargetContainer.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) rename osu.Game/{Screens/Play/HUD => Skinning}/SkinnableElementTargetContainer.cs (97%) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index b32d117a92..a00557ee4e 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Skinning.Editor diff --git a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs b/osu.Game/Skinning/SkinnableElementTargetContainer.cs similarity index 97% rename from osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs rename to osu.Game/Skinning/SkinnableElementTargetContainer.cs index b2f6d32927..b900fdf3e0 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableElementTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableElementTargetContainer.cs @@ -7,9 +7,9 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Extensions; -using osu.Game.Skinning; +using osu.Game.Screens.Play.HUD; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Skinning { public class SkinnableElementTargetContainer : SkinReloadableDrawable, ISkinnableTarget { From 9df08560b69519f94779e22e2ce6c49433b1808b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 16:07:00 +0900 Subject: [PATCH 555/708] Save skin editor changes on forced exit --- osu.Game/Skinning/Editor/SkinEditor.cs | 4 ++-- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index a00557ee4e..5deebe9800 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -108,7 +108,7 @@ namespace osu.Game.Skinning.Editor { Text = "Save Changes", Width = 140, - Action = save, + Action = Save, }, new DangerousTriangleButton { @@ -192,7 +192,7 @@ namespace osu.Game.Skinning.Editor } } - private void save() + public void Save() { SkinnableElementTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index cbed498a38..2d7cae71ff 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -92,8 +92,10 @@ namespace osu.Game.Skinning.Editor /// public void Reset() { + skinEditor?.Save(); skinEditor?.Hide(); skinEditor?.Expire(); + skinEditor = null; } } From 17e376457610652e9cfac1470eb8748114441022 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 16:38:04 +0900 Subject: [PATCH 556/708] Rename `Settings` to have a more localised name --- .../Edit/{Settings.cs => RoundedContentEditorScreenSettings.cs} | 2 +- osu.Game/Screens/Edit/Timing/ControlPointSettings.cs | 2 +- osu.Game/Screens/Edit/Verify/IssueSettings.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game/Screens/Edit/{Settings.cs => RoundedContentEditorScreenSettings.cs} (94%) diff --git a/osu.Game/Screens/Edit/Settings.cs b/osu.Game/Screens/Edit/RoundedContentEditorScreenSettings.cs similarity index 94% rename from osu.Game/Screens/Edit/Settings.cs rename to osu.Game/Screens/Edit/RoundedContentEditorScreenSettings.cs index 758414333d..06293fbaac 100644 --- a/osu.Game/Screens/Edit/Settings.cs +++ b/osu.Game/Screens/Edit/RoundedContentEditorScreenSettings.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays; namespace osu.Game.Screens.Edit { - public abstract class Settings : CompositeDrawable + public abstract class RoundedContentEditorScreenSettings : CompositeDrawable { [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 36f31d4be4..13f81c75da 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; namespace osu.Game.Screens.Edit.Timing { - public class ControlPointSettings : Settings + public class ControlPointSettings : RoundedContentEditorScreenSettings { protected override IReadOnlyList CreateSections() => new Drawable[] { diff --git a/osu.Game/Screens/Edit/Verify/IssueSettings.cs b/osu.Game/Screens/Edit/Verify/IssueSettings.cs index 68ab51c3c8..506694aa46 100644 --- a/osu.Game/Screens/Edit/Verify/IssueSettings.cs +++ b/osu.Game/Screens/Edit/Verify/IssueSettings.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; namespace osu.Game.Screens.Edit.Verify { - public class IssueSettings : Settings + public class IssueSettings : RoundedContentEditorScreenSettings { private readonly IssueList issueList; From d2e0e8ad948aac6ef137abf81d50963ba8e55297 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 16:53:49 +0900 Subject: [PATCH 557/708] Reverse direction of binding to allow for better abstract class definitions --- ...tEditorScreen.cs => EditorRoundedScreen.cs} | 4 ++-- ...tings.cs => EditorRoundedScreenSettings.cs} | 2 +- ...s => EditorRoundedScreenSettingsSection.cs} | 11 ++--------- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- .../Screens/Edit/Setup/SetupScreenHeader.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupSection.cs | 2 +- .../Edit/Timing/ControlPointSettings.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- .../Edit/Verify/InterpretationSection.cs | 11 +++++------ osu.Game/Screens/Edit/Verify/IssueList.cs | 5 +++-- osu.Game/Screens/Edit/Verify/IssueSettings.cs | 13 +++---------- osu.Game/Screens/Edit/Verify/IssueTable.cs | 5 ++++- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 18 +++++++++++------- .../Screens/Edit/Verify/VisibilitySection.cs | 14 ++++++-------- 14 files changed, 42 insertions(+), 51 deletions(-) rename osu.Game/Screens/Edit/{RoundedContentEditorScreen.cs => EditorRoundedScreen.cs} (93%) rename osu.Game/Screens/Edit/{RoundedContentEditorScreenSettings.cs => EditorRoundedScreenSettings.cs} (94%) rename osu.Game/Screens/Edit/{Verify/Section.cs => EditorRoundedScreenSettingsSection.cs} (88%) diff --git a/osu.Game/Screens/Edit/RoundedContentEditorScreen.cs b/osu.Game/Screens/Edit/EditorRoundedScreen.cs similarity index 93% rename from osu.Game/Screens/Edit/RoundedContentEditorScreen.cs rename to osu.Game/Screens/Edit/EditorRoundedScreen.cs index a55ae3f635..c6ced02021 100644 --- a/osu.Game/Screens/Edit/RoundedContentEditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreen.cs @@ -10,7 +10,7 @@ using osu.Game.Overlays; namespace osu.Game.Screens.Edit { - public class RoundedContentEditorScreen : EditorScreen + public class EditorRoundedScreen : EditorScreen { public const int HORIZONTAL_PADDING = 100; @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit protected override Container Content => roundedContent; - public RoundedContentEditorScreen(EditorScreenMode mode) + public EditorRoundedScreen(EditorScreenMode mode) : base(mode) { ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); diff --git a/osu.Game/Screens/Edit/RoundedContentEditorScreenSettings.cs b/osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs similarity index 94% rename from osu.Game/Screens/Edit/RoundedContentEditorScreenSettings.cs rename to osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs index 06293fbaac..cb17484d27 100644 --- a/osu.Game/Screens/Edit/RoundedContentEditorScreenSettings.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays; namespace osu.Game.Screens.Edit { - public abstract class RoundedContentEditorScreenSettings : CompositeDrawable + public abstract class EditorRoundedScreenSettings : CompositeDrawable { [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) diff --git a/osu.Game/Screens/Edit/Verify/Section.cs b/osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs similarity index 88% rename from osu.Game/Screens/Edit/Verify/Section.cs rename to osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs index f6815a05a1..87ed98a439 100644 --- a/osu.Game/Screens/Edit/Verify/Section.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs @@ -9,22 +9,15 @@ using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osuTK; -namespace osu.Game.Screens.Edit.Verify +namespace osu.Game.Screens.Edit { - public abstract class Section : CompositeDrawable + public abstract class EditorRoundedScreenSettingsSection : CompositeDrawable { private const int header_height = 50; - protected readonly IssueList IssueList; - protected FillFlowContainer Flow; protected abstract string Header { get; } - protected Section(IssueList issueList) - { - IssueList = issueList; - } - [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 0af7cf095b..5bbec2574f 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Edit.Setup { - public class SetupScreen : RoundedContentEditorScreen + public class SetupScreen : EditorRoundedScreen { [Cached] private SectionsContainer sections = new SectionsContainer(); diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs index 10daacc359..2d0afda001 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Edit.Setup public SetupScreenTabControl() { - TabContainer.Margin = new MarginPadding { Horizontal = RoundedContentEditorScreen.HORIZONTAL_PADDING }; + TabContainer.Margin = new MarginPadding { Horizontal = EditorRoundedScreen.HORIZONTAL_PADDING }; AddInternal(background = new Box { diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index b3ae15900f..8964e651df 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Edit.Setup Padding = new MarginPadding { Vertical = 10, - Horizontal = RoundedContentEditorScreen.HORIZONTAL_PADDING + Horizontal = EditorRoundedScreen.HORIZONTAL_PADDING }; InternalChild = new FillFlowContainer diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 13f81c75da..48639789af 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; namespace osu.Game.Screens.Edit.Timing { - public class ControlPointSettings : RoundedContentEditorScreenSettings + public class ControlPointSettings : EditorRoundedScreenSettings { protected override IReadOnlyList CreateSections() => new Drawable[] { diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 9f26dece08..a4193d5084 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Timing { - public class TimingScreen : RoundedContentEditorScreen + public class TimingScreen : EditorRoundedScreen { [Cached] private Bindable selectedGroup = new Bindable(); diff --git a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs index fa0bde9ddc..7991786542 100644 --- a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs +++ b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs @@ -8,12 +8,10 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Verify { - internal class InterpretationSection : Section + internal class InterpretationSection : EditorRoundedScreenSettingsSection { - public InterpretationSection(IssueList issueList) - : base(issueList) - { - } + [Resolved] + private VerifyScreen verify { get; set; } protected override string Header => "Interpretation"; @@ -26,7 +24,8 @@ namespace osu.Game.Screens.Edit.Verify Origin = Anchor.CentreLeft, TooltipText = "Affects checks that depend on difficulty level" }; - dropdown.Current.BindTo(IssueList.InterpretedDifficulty); + + dropdown.Current.BindTo(verify.InterpretedDifficulty); Flow.Add(dropdown); } diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 3e836c4010..befffdd9db 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -18,6 +18,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Verify { + [Cached] public class IssueList : CompositeDrawable { private IssueTable table; @@ -32,7 +33,7 @@ namespace osu.Game.Screens.Edit.Verify private EditorBeatmap beatmap { get; set; } [Resolved] - private Bindable selectedIssue { get; set; } + private VerifyScreen verify { get; set; } public Dictionary> ShowType { get; set; } @@ -55,7 +56,7 @@ namespace osu.Game.Screens.Edit.Verify generalVerifier = new BeatmapVerifier(); rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); - InterpretedDifficulty = new Bindable(beatmap.BeatmapInfo.DifficultyRating); + InterpretedDifficulty = verify.InterpretedDifficulty.GetBoundCopy(); RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Edit/Verify/IssueSettings.cs b/osu.Game/Screens/Edit/Verify/IssueSettings.cs index 506694aa46..ae3ef7e0b0 100644 --- a/osu.Game/Screens/Edit/Verify/IssueSettings.cs +++ b/osu.Game/Screens/Edit/Verify/IssueSettings.cs @@ -6,19 +6,12 @@ using osu.Framework.Graphics; namespace osu.Game.Screens.Edit.Verify { - public class IssueSettings : RoundedContentEditorScreenSettings + public class IssueSettings : EditorRoundedScreenSettings { - private readonly IssueList issueList; - - public IssueSettings(IssueList issueList) - { - this.issueList = issueList; - } - protected override IReadOnlyList CreateSections() => new Drawable[] { - new InterpretationSection(issueList), - new VisibilitySection(issueList) + new InterpretationSection(), + new VisibilitySection() }; } } diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 44244028c9..05a8fdd26d 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -18,7 +18,9 @@ namespace osu.Game.Screens.Edit.Verify public class IssueTable : EditorTable { [Resolved] - private Bindable selectedIssue { get; set; } + private VerifyScreen verify { get; set; } + + private Bindable selectedIssue; [Resolved] private EditorClock clock { get; set; } @@ -71,6 +73,7 @@ namespace osu.Game.Screens.Edit.Verify { base.LoadComplete(); + selectedIssue = verify.SelectedIssue.GetBoundCopy(); selectedIssue.BindValueChanged(issue => { foreach (var b in BackgroundFlow) b.Selected = b.Item == issue.NewValue; diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index ed4658da8e..afd0c8760d 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -5,14 +5,19 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Screens.Edit.Verify { - public class VerifyScreen : RoundedContentEditorScreen + [Cached] + public class VerifyScreen : EditorRoundedScreen { - [Cached] - private Bindable selectedIssue = new Bindable(); + public readonly Bindable SelectedIssue = new Bindable(); + + public readonly Bindable InterpretedDifficulty = new Bindable(); + + public IssueList IssueList { get; private set; } public VerifyScreen() : base(EditorScreenMode.Verify) @@ -22,8 +27,7 @@ namespace osu.Game.Screens.Edit.Verify [BackgroundDependencyLoader] private void load() { - IssueList issueList; - + IssueList = new IssueList(); Child = new Container { RelativeSizeAxes = Axes.Both, @@ -39,8 +43,8 @@ namespace osu.Game.Screens.Edit.Verify { new Drawable[] { - issueList = new IssueList(), - new IssueSettings(issueList), + IssueList, + new IssueSettings(), }, } } diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index f849426800..ce755bdcb4 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -9,19 +9,17 @@ using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Screens.Edit.Verify { - internal class VisibilitySection : Section + internal class VisibilitySection : EditorRoundedScreenSettingsSection { - public VisibilitySection(IssueList issueList) - : base(issueList) - { - } + [Resolved] + private VerifyScreen verify { get; set; } protected override string Header => "Visibility"; [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { - foreach (IssueType issueType in IssueList.ShowType.Keys) + foreach (IssueType issueType in verify.IssueList.ShowType.Keys) { var checkbox = new SettingsCheckbox { @@ -30,8 +28,8 @@ namespace osu.Game.Screens.Edit.Verify LabelText = issueType.ToString() }; - checkbox.Current.BindTo(IssueList.ShowType[issueType]); - checkbox.Current.BindValueChanged(_ => IssueList.Refresh()); + checkbox.Current.BindTo(verify.IssueList.ShowType[issueType]); + checkbox.Current.BindValueChanged(_ => verify.IssueList.Refresh()); Flow.Add(checkbox); } } From be187e8ebdd8d981b1c3c1620960e1aaca27d44b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 17:42:04 +0900 Subject: [PATCH 558/708] Avoid hard crash if `Save()` is called before preparing for mutation --- osu.Game/Skinning/Editor/SkinEditor.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 5deebe9800..fcb1392ed5 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -38,6 +38,8 @@ namespace osu.Game.Skinning.Editor [Resolved] private OsuColour colours { get; set; } + private bool hasBegunMutating; + public SkinEditor(Drawable targetScreen) { this.targetScreen = targetScreen; @@ -139,7 +141,11 @@ namespace osu.Game.Skinning.Editor // schedule ensures this only happens when the skin editor is visible. // also avoid some weird endless recursion / bindable feedback loop (something to do with tracking skins across three different bindable types). // probably something which will be factored out in a future database refactor so not too concerning for now. - currentSkin.BindValueChanged(skin => Scheduler.AddOnce(skinChanged), true); + currentSkin.BindValueChanged(skin => + { + hasBegunMutating = false; + Scheduler.AddOnce(skinChanged); + }, true); } private void skinChanged() @@ -161,6 +167,7 @@ namespace osu.Game.Skinning.Editor }); skins.EnsureMutableSkin(); + hasBegunMutating = true; } private void placeComponent(Type type) @@ -194,6 +201,9 @@ namespace osu.Game.Skinning.Editor public void Save() { + if (!hasBegunMutating) + return; + SkinnableElementTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); foreach (var t in targetContainers) From 2f55d1e5ab9c734bb07519849d6ca4c31f32b375 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 17:42:12 +0900 Subject: [PATCH 559/708] Also save on skin switch --- osu.Game/Skinning/Editor/SkinEditor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index fcb1392ed5..9e50aab829 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -143,6 +143,8 @@ namespace osu.Game.Skinning.Editor // probably something which will be factored out in a future database refactor so not too concerning for now. currentSkin.BindValueChanged(skin => { + Save(); + hasBegunMutating = false; Scheduler.AddOnce(skinChanged); }, true); From 80e231d90ad9a25444b7a2e2c1d43413cbaf327e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 12 May 2021 11:07:31 +0300 Subject: [PATCH 560/708] Add failing test case --- .../Visual/Editing/TestSceneComposeSelectBox.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index e383aa8008..d5cfeb1878 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -167,5 +167,21 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox, new Vector2(20))); AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0); } + + /// + /// Tests that hovering over two handles instantaneously from one to another does not crash or cause issues to the visibility state. + /// + [Test] + public void TestHoverOverTwoHandlesInstantaneously() + { + AddStep("hover over top-left scale handle", () => + InputManager.MoveMouseTo(this.ChildrenOfType().Single(s => s.Anchor == Anchor.TopLeft))); + AddStep("hover over top-right scale handle", () => + InputManager.MoveMouseTo(this.ChildrenOfType().Single(s => s.Anchor == Anchor.TopRight))); + AddUntilStep("top-left rotation handle hidden", () => + this.ChildrenOfType().Single(r => r.Anchor == Anchor.TopLeft).Alpha == 0); + AddUntilStep("top-right rotation handle shown", () => + this.ChildrenOfType().Single(r => r.Anchor == Anchor.TopRight).Alpha == 1); + } } } From 96d3586294dbfef0f9af5b7a9e70e00a868d0600 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 12 May 2021 11:30:12 +0300 Subject: [PATCH 561/708] Fix rotation handle visibility logic not handling two handles hovered at once --- .../Compose/Components/SelectionBoxDragHandleContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs index 456f72878d..397158b9f6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxDragHandleContainer.cs @@ -84,8 +84,8 @@ namespace osu.Game.Screens.Edit.Compose.Components if (activeHandle?.IsHeld == true) return; - activeHandle = rotationHandles.SingleOrDefault(h => h.IsHeld || h.IsHovered); - activeHandle ??= allDragHandles.SingleOrDefault(h => h.IsHovered); + activeHandle = rotationHandles.FirstOrDefault(h => h.IsHeld || h.IsHovered); + activeHandle ??= allDragHandles.FirstOrDefault(h => h.IsHovered); if (activeHandle != null) { From 088335a0359f73ecb10625c5bcccbaefff9edbd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 17:45:51 +0900 Subject: [PATCH 562/708] Revert "Also save on skin switch" This reverts commit 2f55d1e5ab9c734bb07519849d6ca4c31f32b375. --- osu.Game/Skinning/Editor/SkinEditor.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 9e50aab829..fcb1392ed5 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -143,8 +143,6 @@ namespace osu.Game.Skinning.Editor // probably something which will be factored out in a future database refactor so not too concerning for now. currentSkin.BindValueChanged(skin => { - Save(); - hasBegunMutating = false; Scheduler.AddOnce(skinChanged); }, true); From 0a895fff158ab57c8dc0e9a4ff9e6d9f5a7b218e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 18:53:25 +0900 Subject: [PATCH 563/708] Remove remaining test usage of `SkinnableXXX` HUD components --- .../Visual/Gameplay/TestSceneComboCounter.cs | 8 ++------ .../TestSceneSkinnableAccuracyCounter.cs | 3 ++- .../TestSceneSkinnableHealthDisplay.cs | 11 ++-------- .../TestSceneSkinnableScoreCounter.cs | 11 +++------- .../Play/HUD/SkinnableAccuracyCounter.cs | 16 --------------- .../Screens/Play/HUD/SkinnableComboCounter.cs | 16 --------------- .../Play/HUD/SkinnableHealthDisplay.cs | 16 --------------- .../Screens/Play/HUD/SkinnableScoreCounter.cs | 16 --------------- osu.Game/Skinning/DefaultSkin.cs | 20 +++++++++++++++++++ osu.Game/Skinning/SkinnableDrawable.cs | 4 ++-- 10 files changed, 31 insertions(+), 90 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs delete mode 100644 osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs delete mode 100644 osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs delete mode 100644 osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs index b0a0b5189f..b22af0f7ac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneComboCounter.cs @@ -1,22 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneComboCounter : SkinnableTestScene { - private IEnumerable comboCounters => CreatedDrawables.OfType(); - protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); [Cached] @@ -25,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUpSteps] public void SetUpSteps() { - AddStep("Create combo counters", () => SetContents(() => new SkinnableComboCounter())); + AddStep("Create combo counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.ComboCounter)))); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs index 6a8a2187f9..d9139299ea 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay { @@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void SetUpSteps() { AddStep("Set initial accuracy", () => scoreProcessor.Accuracy.Value = 1); - AddStep("Create accuracy counters", () => SetContents(() => new SkinnableAccuracyCounter())); + AddStep("Create accuracy counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)))); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index 4f50613416..ead27bf017 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; @@ -12,14 +10,12 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinnableHealthDisplay : SkinnableTestScene { - private IEnumerable healthDisplays => CreatedDrawables.OfType(); - protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); [Cached(typeof(HealthProcessor))] @@ -28,10 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUpSteps] public void SetUpSteps() { - AddStep("Create health displays", () => - { - SetContents(() => new SkinnableHealthDisplay()); - }); + AddStep("Create health displays", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)))); AddStep(@"Reset all", delegate { healthProcessor.Health.Value = 1; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs index 4f2183711e..8d633c3ca2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableScoreCounter.cs @@ -1,23 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinnableScoreCounter : SkinnableTestScene { - private IEnumerable scoreCounters => CreatedDrawables.OfType(); - protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); [Cached] @@ -26,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUpSteps] public void SetUpSteps() { - AddStep("Create score counters", () => SetContents(() => new SkinnableScoreCounter())); + AddStep("Create score counters", () => SetContents(() => new SkinnableDrawable(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)))); } [Test] @@ -40,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestVeryLargeScore() { - AddStep("set large score", () => scoreCounters.ForEach(counter => scoreProcessor.TotalScore.Value = 1_000_000_000)); + AddStep("set large score", () => scoreProcessor.TotalScore.Value = 1_000_000_000); } } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs deleted file mode 100644 index fcb8fca35d..0000000000 --- a/osu.Game/Screens/Play/HUD/SkinnableAccuracyCounter.cs +++ /dev/null @@ -1,16 +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.Skinning; - -namespace osu.Game.Screens.Play.HUD -{ - public class SkinnableAccuracyCounter : SkinnableDrawable - { - public SkinnableAccuracyCounter() - : base(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter), _ => new DefaultAccuracyCounter()) - { - CentreComponent = false; - } - } -} diff --git a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs deleted file mode 100644 index c62f1460c9..0000000000 --- a/osu.Game/Screens/Play/HUD/SkinnableComboCounter.cs +++ /dev/null @@ -1,16 +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.Skinning; - -namespace osu.Game.Screens.Play.HUD -{ - public class SkinnableComboCounter : SkinnableDrawable - { - public SkinnableComboCounter() - : base(new HUDSkinComponent(HUDSkinComponents.ComboCounter), skinComponent => new DefaultComboCounter()) - { - CentreComponent = false; - } - } -} diff --git a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs deleted file mode 100644 index 3ba6a33276..0000000000 --- a/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs +++ /dev/null @@ -1,16 +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.Skinning; - -namespace osu.Game.Screens.Play.HUD -{ - public class SkinnableHealthDisplay : SkinnableDrawable - { - public SkinnableHealthDisplay() - : base(new HUDSkinComponent(HUDSkinComponents.HealthDisplay), _ => new DefaultHealthDisplay()) - { - CentreComponent = false; - } - } -} diff --git a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs b/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs deleted file mode 100644 index cc9a712e97..0000000000 --- a/osu.Game/Screens/Play/HUD/SkinnableScoreCounter.cs +++ /dev/null @@ -1,16 +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.Skinning; - -namespace osu.Game.Screens.Play.HUD -{ - public class SkinnableScoreCounter : SkinnableDrawable - { - public SkinnableScoreCounter() - : base(new HUDSkinComponent(HUDSkinComponents.ScoreCounter), _ => new DefaultScoreCounter()) - { - CentreComponent = false; - } - } -} diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 3de3dc7702..a9ab4e53c8 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -91,6 +91,26 @@ namespace osu.Game.Skinning } return null; + + case HUDSkinComponent hudComponent: + { + switch (hudComponent.Component) + { + case HUDSkinComponents.ComboCounter: + return new DefaultComboCounter(); + + case HUDSkinComponents.ScoreCounter: + return new DefaultScoreCounter(); + + case HUDSkinComponents.AccuracyCounter: + return new DefaultAccuracyCounter(); + + case HUDSkinComponents.HealthDisplay: + return new DefaultHealthDisplay(); + } + + return null; + } } return base.GetDrawableComponent(component); diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 5a48bc4baf..77f7cf5c3f 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -42,7 +42,7 @@ namespace osu.Game.Skinning /// A function to create the default skin implementation of this element. /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. /// How (if at all) the should be resize to fit within our own bounds. - public SkinnableDrawable(ISkinComponent component, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) + public SkinnableDrawable(ISkinComponent component, Func defaultImplementation = null, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) : this(component, allowFallback, confineMode) { createDefault = defaultImplementation; @@ -68,7 +68,7 @@ namespace osu.Game.Skinning private bool isDefault; - protected virtual Drawable CreateDefault(ISkinComponent component) => createDefault(component); + protected virtual Drawable CreateDefault(ISkinComponent component) => createDefault?.Invoke(component) ?? Empty(); /// /// Whether to apply size restrictions (specified via ) to the default implementation. From 75227e5a70f21cb941e249c6e5d80b15b62e956a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 18:55:48 +0900 Subject: [PATCH 564/708] Change default skin to use component lookup for conformity --- osu.Game/Skinning/DefaultSkin.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index a9ab4e53c8..2715c9fce2 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -80,10 +80,10 @@ namespace osu.Game.Skinning { Children = new Drawable[] { - new DefaultComboCounter(), - new DefaultScoreCounter(), - new DefaultAccuracyCounter(), - new DefaultHealthDisplay(), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ComboCounter)), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)), } }; From 55e1f97f5947dc8e1d65c14935b4a51eaae7c4ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 19:06:28 +0900 Subject: [PATCH 565/708] Remove unused using statement --- .../Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs | 1 - osu.Game/Skinning/DefaultSkin.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs index d9139299ea..6f4e6a2420 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableAccuracyCounter.cs @@ -7,7 +7,6 @@ using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 2715c9fce2..3dcfbcda13 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -78,7 +78,7 @@ namespace osu.Game.Skinning } }) { - Children = new Drawable[] + Children = new[] { GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ComboCounter)), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)), From 7bac81f39487c446d515e5454f49b402680e38ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 May 2021 19:37:00 +0900 Subject: [PATCH 566/708] Fix incorrect inline comments Co-authored-by: Salman Ahmed --- 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 23411f2b3d..58fa255508 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -85,13 +85,13 @@ namespace osu.Game.Skinning.Editor protected override void OnSelected() { - // base logic hides selected blueprints when not selected, but timeline doesn't do that. + // base logic hides selected blueprints when not selected, but skin blueprints don't do that. updateSelectedState(); } protected override void OnDeselected() { - // base logic hides selected blueprints when not selected, but timeline doesn't do that. + // base logic hides selected blueprints when not selected, but skin blueprints don't do that. updateSelectedState(); } From 4464204e336688bcfce0266c2014ec20a7978e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 May 2021 22:18:15 +0200 Subject: [PATCH 567/708] Mark all skin ctors used via reflection in `SkinInfo.CreateInstance()` --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 ++ osu.Game/Skinning/DefaultSkin.cs | 2 ++ osu.Game/Skinning/LegacySkin.cs | 1 + 3 files changed, 5 insertions(+) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 564be8630e..7adf53e5e7 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.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 JetBrains.Annotations; using osu.Framework.IO.Stores; using osu.Game.IO; using osuTK.Graphics; @@ -9,6 +10,7 @@ namespace osu.Game.Skinning { public class DefaultLegacySkin : LegacySkin { + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public DefaultLegacySkin(IResourceStore storage, IStorageResourceProvider resources) : this(Info, storage, resources) { diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index fcd874d6ca..745bdf0bb2 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,6 +21,7 @@ namespace osu.Game.Skinning { } + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public DefaultSkin(SkinInfo skin, IStorageResourceProvider resources) : base(skin) { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index eae3b69233..74250f9684 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -52,6 +52,7 @@ namespace osu.Game.Skinning private readonly Dictionary maniaConfigurations = new Dictionary(); + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public LegacySkin(SkinInfo skin, IStorageResourceProvider resources) : this(skin, new LegacySkinResourceStore(skin, resources.Files), resources, "skin.ini") { From 1b579dd83801849c33ce06a685eab48230cdd648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 May 2021 22:42:26 +0200 Subject: [PATCH 568/708] Extract invariant instantiation info extension method --- osu.Game/Extensions/TypeExtensions.cs | 31 +++++++++++++++++++++++++++ osu.Game/Rulesets/Ruleset.cs | 3 ++- osu.Game/Rulesets/RulesetInfo.cs | 16 +------------- osu.Game/Skinning/SkinInfo.cs | 16 +------------- osu.Game/Skinning/SkinManager.cs | 3 ++- 5 files changed, 37 insertions(+), 32 deletions(-) create mode 100644 osu.Game/Extensions/TypeExtensions.cs diff --git a/osu.Game/Extensions/TypeExtensions.cs b/osu.Game/Extensions/TypeExtensions.cs new file mode 100644 index 0000000000..2e93c81758 --- /dev/null +++ b/osu.Game/Extensions/TypeExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; + +namespace osu.Game.Extensions +{ + internal static class TypeExtensions + { + /// + /// Returns 's + /// with the assembly version, culture and public key token values removed. + /// + /// + /// This method is usually used in extensibility scenarios (i.e. for custom rulesets or skins) + /// when a version-agnostic identifier associated with a C# class - potentially originating from + /// an external assembly - is needed. + /// Leaving only the type and assembly names in such a scenario allows to preserve compatibility + /// across assembly versions. + /// + internal static string GetInvariantInstantiationInfo(this Type type) + { + string assemblyQualifiedName = type.AssemblyQualifiedName; + if (assemblyQualifiedName == null) + throw new ArgumentException($"{type}'s assembly-qualified name is null. Ensure that it is a concrete type and not a generic type parameter.", nameof(type)); + + return string.Join(',', assemblyQualifiedName.Split(',').Take(2)); + } + } +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 7f0c27adfc..7bdf84ace4 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -26,6 +26,7 @@ using JetBrains.Annotations; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Testing; +using osu.Game.Extensions; using osu.Game.Rulesets.Filter; using osu.Game.Screens.Ranking.Statistics; @@ -135,7 +136,7 @@ namespace osu.Game.Rulesets Name = Description, ShortName = ShortName, ID = (this as ILegacyRuleset)?.LegacyID, - InstantiationInfo = GetType().AssemblyQualifiedName, + InstantiationInfo = GetType().GetInvariantInstantiationInfo(), Available = true, }; } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index d5aca8c650..702bf35fa8 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Newtonsoft.Json; using osu.Framework.Testing; @@ -18,20 +17,7 @@ namespace osu.Game.Rulesets public string ShortName { get; set; } - private string instantiationInfo; - - public string InstantiationInfo - { - get => instantiationInfo; - set => instantiationInfo = abbreviateInstantiationInfo(value); - } - - private string abbreviateInstantiationInfo(string value) - { - // exclude version onwards, matching only on namespace and type. - // this is mainly to allow for new versions of already loaded rulesets to "upgrade" from old. - return string.Join(',', value.Split(',').Take(2)); - } + public string InstantiationInfo { get; set; } [JsonIgnore] public bool Available { get; set; } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 2e29808cb4..a61a6dd1ce 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.IO.Stores; using osu.Game.Configuration; using osu.Game.Database; @@ -25,20 +24,7 @@ namespace osu.Game.Skinning public string Creator { get; set; } - private string instantiationInfo; - - public string InstantiationInfo - { - get => instantiationInfo; - set => instantiationInfo = abbreviateInstantiationInfo(value); - } - - private string abbreviateInstantiationInfo(string value) - { - // exclude version onwards, matching only on namespace and type. - // this is mainly to allow for new versions of already loaded rulesets to "upgrade" from old. - return string.Join(',', value.Split(',').Take(2)); - } + public string InstantiationInfo { get; set; } public virtual Skin CreateInstance(IResourceStore legacyDefaultResources, IStorageResourceProvider resources) { diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index dbb9dcb7fc..b4051286aa 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -22,6 +22,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.IO; using osu.Game.IO.Archives; @@ -124,7 +125,7 @@ namespace osu.Game.Skinning var instance = GetSkin(model); - model.InstantiationInfo ??= instance.GetType().AssemblyQualifiedName; + model.InstantiationInfo ??= instance.GetType().GetInvariantInstantiationInfo(); if (model.Name?.Contains(".osk", StringComparison.OrdinalIgnoreCase) == true) populateMetadata(model, instance); From a6aec6e0074db91968c3526c4f9c556fba1c2329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 May 2021 23:34:25 +0200 Subject: [PATCH 569/708] Fix missed `InstantiationInfo` setter usages --- osu.Game/Skinning/DefaultLegacySkin.cs | 3 ++- osu.Game/Skinning/SkinInfo.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 7adf53e5e7..b05c309e4e 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -3,6 +3,7 @@ using JetBrains.Annotations; using osu.Framework.IO.Stores; +using osu.Game.Extensions; using osu.Game.IO; using osuTK.Graphics; @@ -35,7 +36,7 @@ namespace osu.Game.Skinning ID = SkinInfo.CLASSIC_SKIN, // this is temporary until database storage is decided upon. Name = "osu!classic", Creator = "team osu!", - InstantiationInfo = typeof(DefaultLegacySkin).AssemblyQualifiedName, + InstantiationInfo = typeof(DefaultLegacySkin).GetInvariantInstantiationInfo() }; } } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index a61a6dd1ce..bc57a8e71c 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.IO.Stores; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.IO; namespace osu.Game.Skinning @@ -50,7 +51,7 @@ namespace osu.Game.Skinning ID = DEFAULT_SKIN, Name = "osu!lazer", Creator = "team osu!", - InstantiationInfo = typeof(DefaultSkin).AssemblyQualifiedName, + InstantiationInfo = typeof(DefaultSkin).GetInvariantInstantiationInfo() }; public bool Equals(SkinInfo other) => other != null && ID == other.ID; From 27ca7d0f4fa24f8837607dab5f3009459e92b940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 May 2021 23:53:39 +0200 Subject: [PATCH 570/708] Actually annotate the correct ctor --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index b05c309e4e..f30130b1fb 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -11,12 +11,12 @@ namespace osu.Game.Skinning { public class DefaultLegacySkin : LegacySkin { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public DefaultLegacySkin(IResourceStore storage, IStorageResourceProvider resources) : this(Info, storage, resources) { } + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] public DefaultLegacySkin(SkinInfo skin, IResourceStore storage, IStorageResourceProvider resources) : base(skin, storage, resources, string.Empty) { From 56bd8976666ee49318ab22d4dcfa86f5f7b627ae Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 04:29:27 +0200 Subject: [PATCH 571/708] Move `ShowIssueTypes` to `VerifyScreen` --- osu.Game/Screens/Edit/Verify/IssueList.cs | 14 ++------------ osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 8 ++++++++ osu.Game/Screens/Edit/Verify/VisibilitySection.cs | 4 ++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index befffdd9db..f2d3ef9dda 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -35,8 +35,6 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] private VerifyScreen verify { get; set; } - public Dictionary> ShowType { get; set; } - public Bindable InterpretedDifficulty { get; set; } private IBeatmapVerifier rulesetVerifier; @@ -45,14 +43,6 @@ namespace osu.Game.Screens.Edit.Verify [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { - // Reflects the user interface. Only types in this dictionary have configurable visibility. - ShowType = new Dictionary> - { - { IssueType.Warning, new Bindable(true) }, - { IssueType.Error, new Bindable(true) }, - { IssueType.Negligible, new Bindable(false) } - }; - generalVerifier = new BeatmapVerifier(); rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); @@ -116,9 +106,9 @@ namespace osu.Game.Screens.Edit.Verify private IEnumerable filter(IEnumerable issues) { - foreach (IssueType issueType in ShowType.Keys) + foreach (var issueType in verify.ShowIssueType.Keys) { - if (!ShowType[issueType].Value) + if (!verify.ShowIssueType[issueType].Value) issues = issues.Where(issue => issue.Template.Type != issueType); } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index afd0c8760d..cf1a471714 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -17,6 +18,13 @@ namespace osu.Game.Screens.Edit.Verify public readonly Bindable InterpretedDifficulty = new Bindable(); + public readonly Dictionary> ShowIssueType = new Dictionary> + { + { IssueType.Warning, new Bindable(true) }, + { IssueType.Error, new Bindable(true) }, + { IssueType.Negligible, new Bindable(false) } + }; + public IssueList IssueList { get; private set; } public VerifyScreen() diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index ce755bdcb4..71f3740d86 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Edit.Verify [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { - foreach (IssueType issueType in verify.IssueList.ShowType.Keys) + foreach (IssueType issueType in verify.ShowIssueType.Keys) { var checkbox = new SettingsCheckbox { @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit.Verify LabelText = issueType.ToString() }; - checkbox.Current.BindTo(verify.IssueList.ShowType[issueType]); + checkbox.Current.BindTo(verify.ShowIssueType[issueType]); checkbox.Current.BindValueChanged(_ => verify.IssueList.Refresh()); Flow.Add(checkbox); } From 6806e40ad99b1bb6b561765d05858d11e6f5223a Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 04:30:40 +0200 Subject: [PATCH 572/708] Remove unnecessary local variable This now exists in `VerifyScreen`, which we can access from here. --- osu.Game/Screens/Edit/Verify/IssueList.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index f2d3ef9dda..19536401e9 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -35,8 +35,6 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] private VerifyScreen verify { get; set; } - public Bindable InterpretedDifficulty { get; set; } - private IBeatmapVerifier rulesetVerifier; private BeatmapVerifier generalVerifier; @@ -46,8 +44,6 @@ namespace osu.Game.Screens.Edit.Verify generalVerifier = new BeatmapVerifier(); rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); - InterpretedDifficulty = verify.InterpretedDifficulty.GetBoundCopy(); - RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] From dd8423c4c4350b5db811c8338d80235b2fc8a432 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 04:36:20 +0200 Subject: [PATCH 573/708] Set interpreted difficulty to correct default --- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index cf1a471714..963b77baa1 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -35,6 +35,9 @@ namespace osu.Game.Screens.Edit.Verify [BackgroundDependencyLoader] private void load() { + InterpretedDifficulty.Default = EditorBeatmap.BeatmapInfo.DifficultyRating; + InterpretedDifficulty.SetDefault(); + IssueList = new IssueList(); Child = new Container { From fbb76ba5989d10690411561dbec0d74b4c0151d6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 04:50:32 +0200 Subject: [PATCH 574/708] Split `ShowIssueTypes` dict into hidden and configurable lists This way `VerifyScreen` is decoupled from which options `VisibilitySection` provides. Bindings are a bit less neat, though. --- osu.Game/Screens/Edit/Verify/IssueList.cs | 8 +------ osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 8 +------ .../Screens/Edit/Verify/VisibilitySection.cs | 23 ++++++++++++++++--- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 19536401e9..22d410592e 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -102,13 +102,7 @@ namespace osu.Game.Screens.Edit.Verify private IEnumerable filter(IEnumerable issues) { - foreach (var issueType in verify.ShowIssueType.Keys) - { - if (!verify.ShowIssueType[issueType].Value) - issues = issues.Where(issue => issue.Template.Type != issueType); - } - - return issues; + return issues.Where(issue => !verify.HiddenIssueTypes.Contains(issue.Template.Type)); } } } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 963b77baa1..6d7a4a72e2 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -18,12 +17,7 @@ namespace osu.Game.Screens.Edit.Verify public readonly Bindable InterpretedDifficulty = new Bindable(); - public readonly Dictionary> ShowIssueType = new Dictionary> - { - { IssueType.Warning, new Bindable(true) }, - { IssueType.Error, new Bindable(true) }, - { IssueType.Negligible, new Bindable(false) } - }; + public readonly BindableList HiddenIssueTypes = new BindableList { IssueType.Negligible }; public IssueList IssueList { get; private set; } diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index 71f3740d86..3698f51f66 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -14,12 +14,19 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] private VerifyScreen verify { get; set; } + private readonly IssueType[] configurableIssueTypes = + { + IssueType.Warning, + IssueType.Error, + IssueType.Negligible + }; + protected override string Header => "Visibility"; [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { - foreach (IssueType issueType in verify.ShowIssueType.Keys) + foreach (IssueType issueType in configurableIssueTypes) { var checkbox = new SettingsCheckbox { @@ -28,8 +35,18 @@ namespace osu.Game.Screens.Edit.Verify LabelText = issueType.ToString() }; - checkbox.Current.BindTo(verify.ShowIssueType[issueType]); - checkbox.Current.BindValueChanged(_ => verify.IssueList.Refresh()); + checkbox.Current.Default = !verify.HiddenIssueTypes.Contains(issueType); + checkbox.Current.SetDefault(); + checkbox.Current.BindValueChanged(state => + { + if (!state.NewValue) + verify.HiddenIssueTypes.Add(issueType); + else + verify.HiddenIssueTypes.Remove(issueType); + + verify.IssueList.Refresh(); + }); + Flow.Add(checkbox); } } From 5b0309296812f93f50aef02444367f7698cddb8c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 11:53:50 +0900 Subject: [PATCH 575/708] Fix possible test failure --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 69fbd56490..bba7e2b391 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set user ready", () => client.ChangeState(MultiplayerUserState.Ready)); AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); - AddAssert("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle); + AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle); } [Test] From c8d21f2c3fe65f4c5add3e1429c907a370a61b6a Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 05:25:02 +0200 Subject: [PATCH 576/708] Isolate refreshing to `IssueList` --- osu.Game/Screens/Edit/Verify/IssueList.cs | 6 +++--- osu.Game/Screens/Edit/Verify/VisibilitySection.cs | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 22d410592e..7eba50498c 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Edit.Verify new TriangleButton { Text = "Refresh", - Action = Refresh, + Action = refresh, Size = new Vector2(120, 40), Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -83,10 +83,10 @@ namespace osu.Game.Screens.Edit.Verify { base.LoadComplete(); - Refresh(); + verify.HiddenIssueTypes.BindCollectionChanged((o, s) => refresh(), runOnceImmediately: true); } - public void Refresh() + private void refresh() { var issues = generalVerifier.Run(beatmap, workingBeatmap.Value); diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index 3698f51f66..7ec3ecee07 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -43,8 +43,6 @@ namespace osu.Game.Screens.Edit.Verify verify.HiddenIssueTypes.Add(issueType); else verify.HiddenIssueTypes.Remove(issueType); - - verify.IssueList.Refresh(); }); Flow.Add(checkbox); From e86834b74060d5fc7e2e9314142316b74ccf821b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 05:25:20 +0200 Subject: [PATCH 577/708] Use local bound copy for `HiddenIssueTypes` --- osu.Game/Screens/Edit/Verify/VisibilitySection.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index 7ec3ecee07..48bf0523b1 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -26,6 +26,8 @@ namespace osu.Game.Screens.Edit.Verify [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { + var hiddenIssueTypes = verify.HiddenIssueTypes.GetBoundCopy(); + foreach (IssueType issueType in configurableIssueTypes) { var checkbox = new SettingsCheckbox @@ -35,14 +37,14 @@ namespace osu.Game.Screens.Edit.Verify LabelText = issueType.ToString() }; - checkbox.Current.Default = !verify.HiddenIssueTypes.Contains(issueType); + checkbox.Current.Default = !hiddenIssueTypes.Contains(issueType); checkbox.Current.SetDefault(); checkbox.Current.BindValueChanged(state => { if (!state.NewValue) - verify.HiddenIssueTypes.Add(issueType); + hiddenIssueTypes.Add(issueType); else - verify.HiddenIssueTypes.Remove(issueType); + hiddenIssueTypes.Remove(issueType); }); Flow.Add(checkbox); From 04c1585eb24ef0fa07333bc354c7122dc728e8df Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 05:38:45 +0200 Subject: [PATCH 578/708] Use more consistent lambda discards --- osu.Game/Screens/Edit/Verify/IssueList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 7eba50498c..03cd22ad54 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Edit.Verify { base.LoadComplete(); - verify.HiddenIssueTypes.BindCollectionChanged((o, s) => refresh(), runOnceImmediately: true); + verify.HiddenIssueTypes.BindCollectionChanged((_, __) => refresh(), runOnceImmediately: true); } private void refresh() From e80d8f69220509dc78a9fbdedd9eef797c27c7f1 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 05:46:47 +0200 Subject: [PATCH 579/708] Keep track of local bound copy --- osu.Game/Screens/Edit/Verify/VisibilitySection.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index 48bf0523b1..cb7deb8566 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Settings; @@ -21,12 +22,14 @@ namespace osu.Game.Screens.Edit.Verify IssueType.Negligible }; + private BindableList hiddenIssueTypes; + protected override string Header => "Visibility"; [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { - var hiddenIssueTypes = verify.HiddenIssueTypes.GetBoundCopy(); + hiddenIssueTypes = verify.HiddenIssueTypes.GetBoundCopy(); foreach (IssueType issueType in configurableIssueTypes) { From 6caf4e38790298f24837a0764fe5bfa778118674 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 12:57:28 +0900 Subject: [PATCH 580/708] Add xmldoc to `SkinnableInfo` --- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 2de3f1b729..2e9235df97 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Extensions; @@ -33,10 +34,15 @@ namespace osu.Game.Screens.Play.HUD public List Children { get; } = new List(); + [JsonConstructor] public SkinnableInfo() { } + /// + /// Construct a new instance populating all attributes from the provided drawable. + /// + /// The drawable which attributes should be sourced from. public SkinnableInfo(Drawable component) { Type = component.GetType(); @@ -47,13 +53,17 @@ namespace osu.Game.Screens.Play.HUD Anchor = component.Anchor; Origin = component.Origin; - if (component is Container container) + if (component is Container container) { - foreach (var child in container.Children.OfType().OfType()) + foreach (var child in container.OfType().OfType()) Children.Add(child.CreateSerialisedInformation()); } } + /// + /// Construct an instance of the drawable with all attributes applied. + /// + /// The new instance. public Drawable CreateInstance() { Drawable d = (Drawable)Activator.CreateInstance(Type); From ee0a6ba93e25df5425480f0427826b182055f6c4 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 05:59:49 +0200 Subject: [PATCH 581/708] Use local bound copy in `InterpretationSection` as well Else we're relying on the `VerifyScreen`'s bindable instance, and by extension the `VerifyScreen` instance itself. --- osu.Game/Screens/Edit/Verify/InterpretationSection.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs index 7991786542..1bdb528473 100644 --- a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs +++ b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Overlays.Settings; @@ -15,9 +16,13 @@ namespace osu.Game.Screens.Edit.Verify protected override string Header => "Interpretation"; + private Bindable interpretedDifficulty; + [BackgroundDependencyLoader] private void load() { + interpretedDifficulty = verify.InterpretedDifficulty.GetBoundCopy(); + var dropdown = new SettingsEnumDropdown { Anchor = Anchor.CentreLeft, @@ -25,7 +30,7 @@ namespace osu.Game.Screens.Edit.Verify TooltipText = "Affects checks that depend on difficulty level" }; - dropdown.Current.BindTo(verify.InterpretedDifficulty); + dropdown.Current.BindTo(interpretedDifficulty); Flow.Add(dropdown); } From fb305130deb9ba12229f36bb436635a04ac646d5 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 06:00:21 +0200 Subject: [PATCH 582/708] Also refresh when interpreted difficulty changes --- osu.Game/Screens/Edit/Verify/IssueList.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 03cd22ad54..407c1e3bc7 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -83,7 +83,10 @@ namespace osu.Game.Screens.Edit.Verify { base.LoadComplete(); - verify.HiddenIssueTypes.BindCollectionChanged((_, __) => refresh(), runOnceImmediately: true); + verify.InterpretedDifficulty.BindValueChanged(_ => refresh()); + verify.HiddenIssueTypes.BindCollectionChanged((_, __) => refresh()); + + refresh(); } private void refresh() From a38cb61b085329cf1712899674ace8c8de68a2d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:02:55 +0900 Subject: [PATCH 583/708] Remove duplicated call to `base.GetDrawableComponent` --- osu.Game/Skinning/DefaultSkin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 55f34ba1c6..d6ca5e9009 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -92,10 +92,10 @@ namespace osu.Game.Skinning return skinnableTargetWrapper; } - return null; + break; } - return base.GetDrawableComponent(component); + return null; } public override IBindable GetConfig(TLookup lookup) From 2bf8635ffd5d44210b6d6638482c089bbd3f611a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:03:23 +0900 Subject: [PATCH 584/708] Move field upwards in class --- osu.Game/Skinning/Editor/SkinBlueprintContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 9890b8e8b6..09bbf7ffd5 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -19,6 +19,8 @@ namespace osu.Game.Skinning.Editor { private readonly Drawable target; + private readonly List> targetComponents = new List>(); + public SkinBlueprintContainer(Drawable target) { this.target = target; @@ -30,8 +32,6 @@ namespace osu.Game.Skinning.Editor SelectedItems.BindTo(editor.SelectedComponents); } - private readonly List> targetComponents = new List>(); - protected override void LoadComplete() { base.LoadComplete(); From 469a7f5d2a07c434aface5cff1a1fdae987c5072 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:04:17 +0900 Subject: [PATCH 585/708] Reorder fields in `SkinEditor` --- osu.Game/Skinning/Editor/SkinEditor.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index a00557ee4e..72dacad310 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -22,19 +22,19 @@ namespace osu.Game.Skinning.Editor { public const double TRANSITION_DURATION = 500; + public readonly BindableList SelectedComponents = new BindableList(); + + protected override bool StartHidden => true; + private readonly Drawable targetScreen; private OsuTextFlowContainer headerText; - protected override bool StartHidden => true; - - public readonly BindableList SelectedComponents = new BindableList(); + private Bindable currentSkin; [Resolved] private SkinManager skins { get; set; } - private Bindable currentSkin; - [Resolved] private OsuColour colours { get; set; } From 992a052426da407f794bb3d241d30338d51a8ce8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:07:06 +0900 Subject: [PATCH 586/708] Remove stray comment --- osu.Game/Skinning/Skin.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 10481e4efd..1fb1e3b99f 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -39,8 +39,6 @@ namespace osu.Game.Skinning { SkinInfo = skin; - // may be null for default skin. - // we may want to move this to some kind of async operation in the future. foreach (SkinnableTarget skinnableTarget in Enum.GetValues(typeof(SkinnableTarget))) { From 47948d7b34c860e17d73579ac4ee6e91d69e3506 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 06:08:48 +0200 Subject: [PATCH 587/708] Set default for bindable in object initializer Fixes the CI failure. --- osu.Game/Screens/Edit/Verify/VisibilitySection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index cb7deb8566..f942621d2a 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -37,10 +37,10 @@ namespace osu.Game.Screens.Edit.Verify { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - LabelText = issueType.ToString() + LabelText = issueType.ToString(), + Current = { Default = !hiddenIssueTypes.Contains(issueType) } }; - checkbox.Current.Default = !hiddenIssueTypes.Contains(issueType); checkbox.Current.SetDefault(); checkbox.Current.BindValueChanged(state => { From c93ed541f3571aa3d9991949d75f29b74fa46def Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:09:33 +0900 Subject: [PATCH 588/708] Add xmldoc and tidy up logic in `Skin` --- osu.Game/Skinning/Skin.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 1fb1e3b99f..64c848fbfa 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -61,11 +61,19 @@ namespace osu.Game.Skinning } } + /// + /// Remove all stored customisations for the provided target. + /// + /// The target container to reset. public void ResetDrawableTarget(SkinnableElementTargetContainer targetContainer) { DrawableComponentInfo.Remove(targetContainer.Target); } + /// + /// Update serialised information for the provided target. + /// + /// The target container to serialise to this skin. public void UpdateDrawableTarget(SkinnableElementTargetContainer targetContainer) { DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSerialisedChildren().ToArray(); @@ -76,10 +84,7 @@ namespace osu.Game.Skinning switch (component) { case SkinnableTargetComponent target: - - var skinnableTarget = target.Target; - - if (!DrawableComponentInfo.TryGetValue(skinnableTarget, out var skinnableInfo)) + if (!DrawableComponentInfo.TryGetValue(target.Target, out var skinnableInfo)) return null; return new SkinnableTargetWrapper From 581e7940c7331ab89f45cc6234089417c1e5bdd6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:13:22 +0900 Subject: [PATCH 589/708] Add xmldoc to `SkinnableElementTargetContainer` --- osu.Game/Skinning/SkinnableElementTargetContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Skinning/SkinnableElementTargetContainer.cs b/osu.Game/Skinning/SkinnableElementTargetContainer.cs index b900fdf3e0..f447800a33 100644 --- a/osu.Game/Skinning/SkinnableElementTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableElementTargetContainer.cs @@ -26,6 +26,9 @@ namespace osu.Game.Skinning Target = target; } + /// + /// Reload all components in this container from the current skin. + /// public void Reload() { ClearInternal(); @@ -43,6 +46,12 @@ namespace osu.Game.Skinning } } + /// + /// Add a new skinnable component to this target. + /// + /// The component to add. + /// Thrown when attempting to add an element to a target which is not supported by the current skin. + /// Thrown if the provided instance is not a . public void Add(ISkinnableComponent component) { if (content == null) From 3b862798e985edc8ff51ba99b4d2961ccdede31b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:14:49 +0900 Subject: [PATCH 590/708] Standardise naming of methods related to SkinnableInfo --- osu.Game/Extensions/DrawableExtensions.cs | 4 ++-- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 4 ++-- osu.Game/Skinning/Skin.cs | 2 +- osu.Game/Skinning/SkinnableElementTargetContainer.cs | 7 +++++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 0ec96a876e..2ac6e6ff22 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -46,9 +46,9 @@ namespace osu.Game.Extensions public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) => drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta); - public static SkinnableInfo CreateSerialisedInformation(this Drawable component) => new SkinnableInfo(component); + public static SkinnableInfo CreateSkinnableInfo(this Drawable component) => new SkinnableInfo(component); - public static void ApplySerialisedInformation(this Drawable component, SkinnableInfo info) + public static void ApplySkinnableInfo(this Drawable component, SkinnableInfo info) { // todo: can probably make this better via deserialisation directly using a common interface. component.Position = info.Position; diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 2e9235df97..678885d096 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play.HUD if (component is Container container) { foreach (var child in container.OfType().OfType()) - Children.Add(child.CreateSerialisedInformation()); + Children.Add(child.CreateSkinnableInfo()); } } @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Play.HUD public Drawable CreateInstance() { Drawable d = (Drawable)Activator.CreateInstance(Type); - d.ApplySerialisedInformation(this); + d.ApplySkinnableInfo(this); return d; } } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 64c848fbfa..4890524b90 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -76,7 +76,7 @@ namespace osu.Game.Skinning /// The target container to serialise to this skin. public void UpdateDrawableTarget(SkinnableElementTargetContainer targetContainer) { - DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSerialisedChildren().ToArray(); + DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray(); } public virtual Drawable GetDrawableComponent(ISkinComponent component) diff --git a/osu.Game/Skinning/SkinnableElementTargetContainer.cs b/osu.Game/Skinning/SkinnableElementTargetContainer.cs index f447800a33..2aea8c0281 100644 --- a/osu.Game/Skinning/SkinnableElementTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableElementTargetContainer.cs @@ -64,8 +64,11 @@ namespace osu.Game.Skinning components.Add(component); } - public IEnumerable CreateSerialisedChildren() => - components.Select(d => ((Drawable)d).CreateSerialisedInformation()); + /// + /// Serialise all children as . + /// + /// The serialised content. + public IEnumerable CreateSkinnableInfo() => components.Select(d => ((Drawable)d).CreateSkinnableInfo()); protected override void SkinChanged(ISkinSource skin, bool allowFallback) { From db19617b8b69a9b9102b8594ed34d881f36d5ede Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:16:20 +0900 Subject: [PATCH 591/708] Add `JsonConstructor` attribute to `SkinnableTargetWrapper` --- osu.Game/Skinning/SkinnableTargetWrapper.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/SkinnableTargetWrapper.cs b/osu.Game/Skinning/SkinnableTargetWrapper.cs index 0d16775f51..2e2ce32a6f 100644 --- a/osu.Game/Skinning/SkinnableTargetWrapper.cs +++ b/osu.Game/Skinning/SkinnableTargetWrapper.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Newtonsoft.Json; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -11,6 +12,7 @@ namespace osu.Game.Skinning /// A container which is serialised and can encapsulate multiple skinnable elements into a single return type (for consumption via . /// Will also optionally apply default cross-element layout dependencies when initialised from a non-deserialised source. /// + [Serializable] public class SkinnableTargetWrapper : Container, ISkinSerialisable { private readonly Action applyDefaults; @@ -25,6 +27,7 @@ namespace osu.Game.Skinning this.applyDefaults = applyDefaults; } + [JsonConstructor] public SkinnableTargetWrapper() { RelativeSizeAxes = Axes.Both; From 23e284b8b31cc59189382d16cb06a72cf16fb00d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:34:03 +0900 Subject: [PATCH 592/708] Change default skin editor shortcut to Ctrl+Shift+S Avoids a conflict with song select's random rewind functionality. As mentioned in #12776. --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index ce945f3bf8..c8227c0887 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -48,7 +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(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor), new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back), From cdcbaf4291e4bbf20b552a305244be92336dbe80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:45:10 +0900 Subject: [PATCH 593/708] Tidy up specification of `SettingsSection` --- .../Screens/Edit/EditorRoundedScreenSettingsSection.cs | 7 ++++--- osu.Game/Screens/Edit/Verify/InterpretationSection.cs | 2 +- osu.Game/Screens/Edit/Verify/VisibilitySection.cs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs b/osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs index 87ed98a439..e17114ebcb 100644 --- a/osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreenSettingsSection.cs @@ -15,8 +15,9 @@ namespace osu.Game.Screens.Edit { private const int header_height = 50; - protected FillFlowContainer Flow; - protected abstract string Header { get; } + protected abstract string HeaderText { get; } + + protected FillFlowContainer Flow { get; private set; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) @@ -36,7 +37,7 @@ namespace osu.Game.Screens.Edit { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = Header, + Text = HeaderText, Font = new FontUsage(size: 25, weight: "bold") } }, diff --git a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs index 1bdb528473..4dca94aacf 100644 --- a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs +++ b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] private VerifyScreen verify { get; set; } - protected override string Header => "Interpretation"; + protected override string HeaderText => "Interpretation"; private Bindable interpretedDifficulty; diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index f942621d2a..7ebfe586a7 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Verify private BindableList hiddenIssueTypes; - protected override string Header => "Visibility"; + protected override string HeaderText => "Visibility"; [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) From c6648112e59f8a5c3429af35f72b99d8ceceb2c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:51:41 +0900 Subject: [PATCH 594/708] Simplify binding flow in `InterpretationSection` --- .../Screens/Edit/Verify/InterpretationSection.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs index 4dca94aacf..7de87737f5 100644 --- a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs +++ b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs @@ -2,7 +2,6 @@ // 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.Beatmaps; using osu.Game.Overlays.Settings; @@ -16,23 +15,16 @@ namespace osu.Game.Screens.Edit.Verify protected override string HeaderText => "Interpretation"; - private Bindable interpretedDifficulty; - [BackgroundDependencyLoader] private void load() { - interpretedDifficulty = verify.InterpretedDifficulty.GetBoundCopy(); - - var dropdown = new SettingsEnumDropdown + Flow.Add(new SettingsEnumDropdown { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - TooltipText = "Affects checks that depend on difficulty level" - }; - - dropdown.Current.BindTo(interpretedDifficulty); - - Flow.Add(dropdown); + TooltipText = "Affects checks that depend on difficulty level", + Current = verify.InterpretedDifficulty.GetBoundCopy() + }); } } } From b81f86bd4d9c088dce4a7c49fb16b768ccc3e669 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 13:53:27 +0900 Subject: [PATCH 595/708] Move DI resolution to inside BDL parameters --- osu.Game/Screens/Edit/Verify/InterpretationSection.cs | 5 +---- osu.Game/Screens/Edit/Verify/VisibilitySection.cs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs index 7de87737f5..9548f8aaa9 100644 --- a/osu.Game/Screens/Edit/Verify/InterpretationSection.cs +++ b/osu.Game/Screens/Edit/Verify/InterpretationSection.cs @@ -10,13 +10,10 @@ namespace osu.Game.Screens.Edit.Verify { internal class InterpretationSection : EditorRoundedScreenSettingsSection { - [Resolved] - private VerifyScreen verify { get; set; } - protected override string HeaderText => "Interpretation"; [BackgroundDependencyLoader] - private void load() + private void load(VerifyScreen verify) { Flow.Add(new SettingsEnumDropdown { diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs index 7ebfe586a7..d049436376 100644 --- a/osu.Game/Screens/Edit/Verify/VisibilitySection.cs +++ b/osu.Game/Screens/Edit/Verify/VisibilitySection.cs @@ -12,9 +12,6 @@ namespace osu.Game.Screens.Edit.Verify { internal class VisibilitySection : EditorRoundedScreenSettingsSection { - [Resolved] - private VerifyScreen verify { get; set; } - private readonly IssueType[] configurableIssueTypes = { IssueType.Warning, @@ -27,7 +24,7 @@ namespace osu.Game.Screens.Edit.Verify protected override string HeaderText => "Visibility"; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours) + private void load(OverlayColourProvider colours, VerifyScreen verify) { hiddenIssueTypes = verify.HiddenIssueTypes.GetBoundCopy(); From e0e9106921fc1b4c30c19c56645e19cd696f455f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 14:54:52 +0900 Subject: [PATCH 596/708] Enable autoplay in skin editor tests --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 2 ++ .../Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 73da76df78..a0b27755b7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private SkinManager skinManager { get; set; } + protected override bool Autoplay => true; + [SetUpSteps] public override void SetUpSteps() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index c7c93b8892..245e190b1f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -1,13 +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.Allocation; 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; @@ -32,12 +30,13 @@ namespace osu.Game.Tests.Visual.Gameplay SetContents(() => { var ruleset = new OsuRuleset(); + var mods = new[] { ruleset.GetAutoplayMod() }; var working = CreateWorkingBeatmap(ruleset.RulesetInfo); - var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo); + var beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap); + var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap, mods); - var hudOverlay = new HUDOverlay(drawableRuleset, Array.Empty()) + var hudOverlay = new HUDOverlay(drawableRuleset, mods) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 4eeeaf6a1aa9588932c2c096c40232d6bb5527d4 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 07:57:32 +0200 Subject: [PATCH 597/708] Keep track of local bound copy --- osu.Game/Screens/Edit/Verify/IssueList.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 2a90834537..f150f4175b 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -35,6 +35,8 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] private VerifyScreen verify { get; set; } + private Bindable interpretedDifficulty; + private IBeatmapVerifier rulesetVerifier; private BeatmapVerifier generalVerifier; private BeatmapVerifierContext context; @@ -45,8 +47,10 @@ namespace osu.Game.Screens.Edit.Verify generalVerifier = new BeatmapVerifier(); rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); + interpretedDifficulty = verify.InterpretedDifficulty.GetBoundCopy(); + context = new BeatmapVerifierContext(workingBeatmap.Value); - context.InterpretedDifficulty.BindTo(verify.InterpretedDifficulty.GetBoundCopy()); + context.InterpretedDifficulty.BindTo(interpretedDifficulty); RelativeSizeAxes = Axes.Both; From b37cb3bdbeadefb2bba7a71575e8be9c535c54d3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 09:00:30 +0200 Subject: [PATCH 598/708] Change interpreted difficulty from bindable to regular value There's no reason for why checks would need this to be bindable. A 1-directional binding is more appropriate. --- osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs | 5 ++--- osu.Game/Screens/Edit/Verify/IssueList.cs | 11 +++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs index 59d43ba3d6..76b74cb993 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Bindables; using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Edit @@ -20,12 +19,12 @@ namespace osu.Game.Rulesets.Edit /// /// The difficulty level which the current beatmap is considered to be. /// - public readonly Bindable InterpretedDifficulty; + public DifficultyRating InterpretedDifficulty; public BeatmapVerifierContext(IWorkingBeatmap workingBeatmap, DifficultyRating difficultyRating = DifficultyRating.ExpertPlus) { WorkingBeatmap = workingBeatmap; - InterpretedDifficulty = new Bindable(difficultyRating); + InterpretedDifficulty = difficultyRating; } } } diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index f150f4175b..abf22ead10 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -35,8 +35,6 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] private VerifyScreen verify { get; set; } - private Bindable interpretedDifficulty; - private IBeatmapVerifier rulesetVerifier; private BeatmapVerifier generalVerifier; private BeatmapVerifierContext context; @@ -47,10 +45,11 @@ namespace osu.Game.Screens.Edit.Verify generalVerifier = new BeatmapVerifier(); rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); - interpretedDifficulty = verify.InterpretedDifficulty.GetBoundCopy(); - - context = new BeatmapVerifierContext(workingBeatmap.Value); - context.InterpretedDifficulty.BindTo(interpretedDifficulty); + context = new BeatmapVerifierContext(workingBeatmap.Value, verify.InterpretedDifficulty.Value); + verify.InterpretedDifficulty.BindValueChanged(change => + { + context.InterpretedDifficulty = change.NewValue; + }); RelativeSizeAxes = Axes.Both; From 5818ed4c8c66bb749a83e700c16fde831ba56b4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 16:41:36 +0900 Subject: [PATCH 599/708] Remove unused DI resolution --- osu.Game/Skinning/LegacyAccuracyCounter.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 3eea5d22d5..493107172f 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -12,9 +12,6 @@ namespace osu.Game.Skinning { public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableComponent { - [Resolved] - private ISkinSource skin { get; set; } - public LegacyAccuracyCounter() { Anchor = Anchor.TopRight; From 19223ba01359b87ebb55b838d47fe6846fd58db4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 16:42:13 +0900 Subject: [PATCH 600/708] Remove left-over debug logging --- osu.Game/Skinning/SkinManager.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 91f3d0c7cf..fbe23482d7 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -194,9 +194,6 @@ namespace osu.Game.Skinning ReplaceFile(skin.SkinInfo, oldFile, streamContent, oldFile.Filename); else AddFile(skin.SkinInfo, streamContent, filename); - - Logger.Log($"Saving out {filename} with {json.Length} bytes"); - Logger.Log(json); } } } From 9dfa48b22ed523f67bc4b29e7a497127b28d6280 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 16:42:43 +0900 Subject: [PATCH 601/708] Fix incorrect exception text --- osu.Game/Skinning/SkinnableElementTargetContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableElementTargetContainer.cs b/osu.Game/Skinning/SkinnableElementTargetContainer.cs index 2aea8c0281..c68dce4109 100644 --- a/osu.Game/Skinning/SkinnableElementTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableElementTargetContainer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Skinning throw new NotSupportedException("Attempting to add a new component to a target container which is not supported by the current skin."); if (!(component is Drawable drawable)) - throw new ArgumentException("Provided argument must be of type {nameof(ISkinnableComponent)}.", nameof(drawable)); + throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(drawable)); content.Add(drawable); components.Add(component); From dd6a06a302a7f38c3889829068589b1260cf944c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 16:43:42 +0900 Subject: [PATCH 602/708] Reword xmldoc to read better --- osu.Game/Skinning/SkinnableTargetWrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableTargetWrapper.cs b/osu.Game/Skinning/SkinnableTargetWrapper.cs index 2e2ce32a6f..d8ad008448 100644 --- a/osu.Game/Skinning/SkinnableTargetWrapper.cs +++ b/osu.Game/Skinning/SkinnableTargetWrapper.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning /// /// Construct a wrapper with defaults that should be applied once. /// - /// A function with default to apply after the initial layout (ie. consuming autosize) + /// A function to apply the default layout. public SkinnableTargetWrapper(Action applyDefaults) : this() { From cdcd31b546ac41806c0f7559d65dd9b24e6323d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 17:03:17 +0900 Subject: [PATCH 603/708] Replace `ISkinSerialisable` with `IsEditable` property --- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 4 ++-- .../Skinning/Editor/SkinBlueprintContainer.cs | 8 ++++++++ osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 3 +++ osu.Game/Skinning/ISkinSerialisable.cs | 15 --------------- osu.Game/Skinning/ISkinnableComponent.cs | 8 +++++++- osu.Game/Skinning/SkinManager.cs | 1 - osu.Game/Skinning/SkinnableTargetWrapper.cs | 4 +++- 7 files changed, 23 insertions(+), 20 deletions(-) delete mode 100644 osu.Game/Skinning/ISkinSerialisable.cs diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 678885d096..a7c5c33f7d 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { /// - /// Serialised information governing custom changes to an . + /// Serialised information governing custom changes to an . /// [Serializable] public class SkinnableInfo : IJsonSerializable @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play.HUD if (component is Container container) { - foreach (var child in container.OfType().OfType()) + foreach (var child in container.OfType().OfType()) Children.Add(child.CreateSkinnableInfo()); } } diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 09bbf7ffd5..31f89ad0c2 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -81,6 +81,14 @@ namespace osu.Game.Skinning.Editor } } + protected override void AddBlueprintFor(ISkinnableComponent item) + { + if (!item.IsEditable) + return; + + base.AddBlueprintFor(item); + } + protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); protected override SelectionBlueprint CreateBlueprintFor(ISkinnableComponent component) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index a000204062..068b2058a6 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -78,6 +78,9 @@ namespace osu.Game.Skinning.Editor Debug.Assert(instance != null); + if (!((ISkinnableComponent)instance).IsEditable) + return null; + return new ToolboxComponentButton(instance); } catch diff --git a/osu.Game/Skinning/ISkinSerialisable.cs b/osu.Game/Skinning/ISkinSerialisable.cs deleted file mode 100644 index d1777512af..0000000000 --- a/osu.Game/Skinning/ISkinSerialisable.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; - -namespace osu.Game.Skinning -{ - /// - /// Denotes a drawable component which should be serialised as part of a skin. - /// Use for components which should be mutable by the user / editor. - /// - public interface ISkinSerialisable : IDrawable - { - } -} diff --git a/osu.Game/Skinning/ISkinnableComponent.cs b/osu.Game/Skinning/ISkinnableComponent.cs index e44c7be13d..65ac2d5849 100644 --- a/osu.Game/Skinning/ISkinnableComponent.cs +++ b/osu.Game/Skinning/ISkinnableComponent.cs @@ -1,12 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; + namespace osu.Game.Skinning { /// /// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications. /// - public interface ISkinnableComponent : ISkinSerialisable + public interface ISkinnableComponent : IDrawable { + /// + /// Whether this component should be editable by an end user. + /// + bool IsEditable => true; } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index fbe23482d7..7b27a7a816 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -19,7 +19,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; -using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; diff --git a/osu.Game/Skinning/SkinnableTargetWrapper.cs b/osu.Game/Skinning/SkinnableTargetWrapper.cs index d8ad008448..28e8d585fc 100644 --- a/osu.Game/Skinning/SkinnableTargetWrapper.cs +++ b/osu.Game/Skinning/SkinnableTargetWrapper.cs @@ -13,8 +13,10 @@ namespace osu.Game.Skinning /// Will also optionally apply default cross-element layout dependencies when initialised from a non-deserialised source. /// [Serializable] - public class SkinnableTargetWrapper : Container, ISkinSerialisable + public class SkinnableTargetWrapper : Container, ISkinnableComponent { + public bool IsEditable => false; + private readonly Action applyDefaults; /// From 7921dc7ece64f04d37e8e96630a4d0f677d29fe9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 17:06:00 +0900 Subject: [PATCH 604/708] Rename `ISkinnableComponent` to `ISkinnableDrawable` --- .../Play/HUD/DefaultAccuracyCounter.cs | 2 +- .../Screens/Play/HUD/DefaultComboCounter.cs | 2 +- .../Screens/Play/HUD/DefaultHealthDisplay.cs | 2 +- .../Screens/Play/HUD/DefaultScoreCounter.cs | 2 +- .../Screens/Play/HUD/LegacyComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 4 ++-- osu.Game/Skinning/Editor/SkinBlueprint.cs | 4 ++-- .../Skinning/Editor/SkinBlueprintContainer.cs | 20 +++++++++---------- .../Skinning/Editor/SkinComponentToolbox.cs | 4 ++-- osu.Game/Skinning/Editor/SkinEditor.cs | 4 ++-- .../Skinning/Editor/SkinSelectionHandler.cs | 10 +++++----- ...ableComponent.cs => ISkinnableDrawable.cs} | 2 +- osu.Game/Skinning/ISkinnableTarget.cs | 6 +++--- osu.Game/Skinning/LegacyAccuracyCounter.cs | 2 +- osu.Game/Skinning/LegacyHealthDisplay.cs | 2 +- osu.Game/Skinning/LegacyScoreCounter.cs | 2 +- .../SkinnableElementTargetContainer.cs | 8 ++++---- osu.Game/Skinning/SkinnableTargetWrapper.cs | 2 +- 18 files changed, 40 insertions(+), 40 deletions(-) rename osu.Game/Skinning/{ISkinnableComponent.cs => ISkinnableDrawable.cs} (90%) diff --git a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs index d8dff89b29..45ba05e036 100644 --- a/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultAccuracyCounter.cs @@ -7,7 +7,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class DefaultAccuracyCounter : GameplayAccuracyCounter, ISkinnableComponent + public class DefaultAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable { [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } diff --git a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs index 13bd045fc0..c4575c5ad0 100644 --- a/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultComboCounter.cs @@ -12,7 +12,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class DefaultComboCounter : RollingCounter, ISkinnableComponent + public class DefaultComboCounter : RollingCounter, ISkinnableDrawable { [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index 241777244b..ed297f0ffc 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -17,7 +17,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour, ISkinnableComponent + public class DefaultHealthDisplay : HealthDisplay, IHasAccentColour, ISkinnableDrawable { /// /// 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 bd18050dfb..16e3642181 100644 --- a/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultScoreCounter.cs @@ -8,7 +8,7 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { - public class DefaultScoreCounter : GameplayScoreCounter, ISkinnableComponent + public class DefaultScoreCounter : GameplayScoreCounter, ISkinnableDrawable { public DefaultScoreCounter() : base(6) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 2565faf423..d64513d41e 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play.HUD /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public class LegacyComboCounter : CompositeDrawable, ISkinnableComponent + public class LegacyComboCounter : CompositeDrawable, ISkinnableDrawable { public Bindable Current { get; } = new BindableInt { MinValue = 0, }; diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index a7c5c33f7d..e08044b14c 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { /// - /// Serialised information governing custom changes to an . + /// Serialised information governing custom changes to an . /// [Serializable] public class SkinnableInfo : IJsonSerializable @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play.HUD if (component is Container container) { - foreach (var child in container.OfType().OfType()) + foreach (var child in container.OfType().OfType()) Children.Add(child.CreateSkinnableInfo()); } } diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index b8dfdbad0a..4be9299699 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Skinning.Editor { - public class SkinBlueprint : SelectionBlueprint + public class SkinBlueprint : SelectionBlueprint { private Container box; @@ -26,7 +26,7 @@ namespace osu.Game.Skinning.Editor protected override bool ShouldBeAlive => (drawable.IsAlive && Item.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); - public SkinBlueprint(ISkinnableComponent component) + public SkinBlueprint(ISkinnableDrawable component) : base(component) { } diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 31f89ad0c2..c0cc2ab40e 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -15,11 +15,11 @@ using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Skinning.Editor { - public class SkinBlueprintContainer : BlueprintContainer + public class SkinBlueprintContainer : BlueprintContainer { private readonly Drawable target; - private readonly List> targetComponents = new List>(); + private readonly List> targetComponents = new List>(); public SkinBlueprintContainer(Drawable target) { @@ -49,7 +49,7 @@ namespace osu.Game.Skinning.Editor foreach (var targetContainer in targetContainers) { - var bindableList = new BindableList { BindTarget = targetContainer.Components }; + var bindableList = new BindableList { BindTarget = targetContainer.Components }; bindableList.BindCollectionChanged(componentsChanged, true); targetComponents.Add(bindableList); @@ -61,27 +61,27 @@ namespace osu.Game.Skinning.Editor switch (e.Action) { case NotifyCollectionChangedAction.Add: - foreach (var item in e.NewItems.Cast()) + foreach (var item in e.NewItems.Cast()) AddBlueprintFor(item); break; case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Reset: - foreach (var item in e.OldItems.Cast()) + foreach (var item in e.OldItems.Cast()) RemoveBlueprintFor(item); break; case NotifyCollectionChangedAction.Replace: - foreach (var item in e.OldItems.Cast()) + foreach (var item in e.OldItems.Cast()) RemoveBlueprintFor(item); - foreach (var item in e.NewItems.Cast()) + foreach (var item in e.NewItems.Cast()) AddBlueprintFor(item); break; } } - protected override void AddBlueprintFor(ISkinnableComponent item) + protected override void AddBlueprintFor(ISkinnableDrawable item) { if (!item.IsEditable) return; @@ -89,9 +89,9 @@ namespace osu.Game.Skinning.Editor base.AddBlueprintFor(item); } - protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); + protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); - protected override SelectionBlueprint CreateBlueprintFor(ISkinnableComponent component) + protected override SelectionBlueprint CreateBlueprintFor(ISkinnableDrawable component) => new SkinBlueprint(component); } } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 068b2058a6..8536cba139 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -56,7 +56,7 @@ namespace osu.Game.Skinning.Editor Spacing = new Vector2(20) }; - var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableComponent).IsAssignableFrom(t)).ToArray(); + var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)).ToArray(); foreach (var type in skinnableTypes) { @@ -78,7 +78,7 @@ namespace osu.Game.Skinning.Editor Debug.Assert(instance != null); - if (!((ISkinnableComponent)instance).IsEditable) + if (!((ISkinnableDrawable)instance).IsEditable) return null; return new ToolboxComponentButton(instance); diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 72dacad310..bf9a6464d0 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -22,7 +22,7 @@ namespace osu.Game.Skinning.Editor { public const double TRANSITION_DURATION = 500; - public readonly BindableList SelectedComponents = new BindableList(); + public readonly BindableList SelectedComponents = new BindableList(); protected override bool StartHidden => true; @@ -165,7 +165,7 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { - if (!(Activator.CreateInstance(type) is ISkinnableComponent component)) + if (!(Activator.CreateInstance(type) is ISkinnableDrawable component)) throw new InvalidOperationException("Attempted to instantiate a component for placement which was not an {typeof(ISkinnableComponent)}."); getTarget(SkinnableTarget.MainHUDComponents)?.Add(component); diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index cf5ece03e9..8ca98c794f 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Skinning.Editor { - public class SkinSelectionHandler : SelectionHandler + public class SkinSelectionHandler : SelectionHandler { public override bool HandleRotation(float angle) { @@ -36,7 +36,7 @@ namespace osu.Game.Skinning.Editor return true; } - public override bool HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { foreach (var c in SelectedBlueprints) { @@ -57,7 +57,7 @@ namespace osu.Game.Skinning.Editor SelectionBox.CanReverse = false; } - protected override void DeleteItems(IEnumerable items) + protected override void DeleteItems(IEnumerable items) { foreach (var i in items) { @@ -66,7 +66,7 @@ namespace osu.Game.Skinning.Editor } } - protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { yield return new OsuMenuItem("Anchor") { @@ -131,7 +131,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/ISkinnableComponent.cs b/osu.Game/Skinning/ISkinnableDrawable.cs similarity index 90% rename from osu.Game/Skinning/ISkinnableComponent.cs rename to osu.Game/Skinning/ISkinnableDrawable.cs index 65ac2d5849..d42b6f71b0 100644 --- a/osu.Game/Skinning/ISkinnableComponent.cs +++ b/osu.Game/Skinning/ISkinnableDrawable.cs @@ -8,7 +8,7 @@ namespace osu.Game.Skinning /// /// Denotes a drawable which, as a drawable, can be adjusted via skinning specifications. /// - public interface ISkinnableComponent : IDrawable + public interface ISkinnableDrawable : IDrawable { /// /// Whether this component should be editable by an end user. diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index ab3c24a1e2..3773b5be24 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -6,7 +6,7 @@ using osu.Framework.Bindables; namespace osu.Game.Skinning { /// - /// Denotes a container which can house s. + /// Denotes a container which can house s. /// public interface ISkinnableTarget { @@ -18,7 +18,7 @@ namespace osu.Game.Skinning /// /// A bindable list of components which are being tracked by this skinnable target. /// - IBindableList Components { get; } + IBindableList Components { get; } /// /// Reload this target from the current skin. @@ -28,6 +28,6 @@ namespace osu.Game.Skinning /// /// Add the provided item to this target. /// - void Add(ISkinnableComponent drawable); + void Add(ISkinnableDrawable drawable); } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 493107172f..16562d9571 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -10,7 +10,7 @@ using osuTK; namespace osu.Game.Skinning { - public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableComponent + public class LegacyAccuracyCounter : GameplayAccuracyCounter, ISkinnableDrawable { public LegacyAccuracyCounter() { diff --git a/osu.Game/Skinning/LegacyHealthDisplay.cs b/osu.Game/Skinning/LegacyHealthDisplay.cs index dfb6ca1a64..c601adc3a0 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 : HealthDisplay, ISkinnableComponent + public class LegacyHealthDisplay : HealthDisplay, ISkinnableDrawable { private const double epic_cutoff = 0.5; diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 8aa48da453..64ea03d59c 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -8,7 +8,7 @@ using osuTK; namespace osu.Game.Skinning { - public class LegacyScoreCounter : GameplayScoreCounter, ISkinnableComponent + public class LegacyScoreCounter : GameplayScoreCounter, ISkinnableDrawable { protected override double RollingDuration => 1000; protected override Easing RollingEasing => Easing.Out; diff --git a/osu.Game/Skinning/SkinnableElementTargetContainer.cs b/osu.Game/Skinning/SkinnableElementTargetContainer.cs index c68dce4109..2a76d3bf7c 100644 --- a/osu.Game/Skinning/SkinnableElementTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableElementTargetContainer.cs @@ -17,9 +17,9 @@ namespace osu.Game.Skinning public SkinnableTarget Target { get; } - public IBindableList Components => components; + public IBindableList Components => components; - private readonly BindableList components = new BindableList(); + private readonly BindableList components = new BindableList(); public SkinnableElementTargetContainer(SkinnableTarget target) { @@ -41,7 +41,7 @@ namespace osu.Game.Skinning LoadComponentAsync(content, wrapper => { AddInternal(wrapper); - components.AddRange(wrapper.Children.OfType()); + components.AddRange(wrapper.Children.OfType()); }); } } @@ -52,7 +52,7 @@ namespace osu.Game.Skinning /// The component to add. /// Thrown when attempting to add an element to a target which is not supported by the current skin. /// Thrown if the provided instance is not a . - public void Add(ISkinnableComponent component) + public void Add(ISkinnableDrawable component) { if (content == null) throw new NotSupportedException("Attempting to add a new component to a target container which is not supported by the current skin."); diff --git a/osu.Game/Skinning/SkinnableTargetWrapper.cs b/osu.Game/Skinning/SkinnableTargetWrapper.cs index 28e8d585fc..58ecac71ab 100644 --- a/osu.Game/Skinning/SkinnableTargetWrapper.cs +++ b/osu.Game/Skinning/SkinnableTargetWrapper.cs @@ -13,7 +13,7 @@ namespace osu.Game.Skinning /// Will also optionally apply default cross-element layout dependencies when initialised from a non-deserialised source. /// [Serializable] - public class SkinnableTargetWrapper : Container, ISkinnableComponent + public class SkinnableTargetWrapper : Container, ISkinnableDrawable { public bool IsEditable => false; From 106fa97a11080329a6097430590866d827dfe1a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 17:07:38 +0900 Subject: [PATCH 605/708] Rename `SkinnableElementTargetContainer` to `SkinnableTargetContainer` --- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++-- osu.Game/Skinning/Editor/SkinEditor.cs | 4 ++-- osu.Game/Skinning/Skin.cs | 4 ++-- ...eElementTargetContainer.cs => SkinnableTargetContainer.cs} | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game/Skinning/{SkinnableElementTargetContainer.cs => SkinnableTargetContainer.cs} (94%) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 887346b5df..a10e91dae8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Play private bool holdingForHUD; - private readonly SkinnableElementTargetContainer mainComponents; + private readonly SkinnableTargetContainer mainComponents; private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter, topRightElements }; @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - mainComponents = new SkinnableElementTargetContainer(SkinnableTarget.MainHUDComponents) + mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents) { RelativeSizeAxes = Axes.Both, }, diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index bf9a6464d0..67285987ef 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -181,7 +181,7 @@ namespace osu.Game.Skinning.Editor private void revert() { - SkinnableElementTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); + SkinnableTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); foreach (var t in targetContainers) { @@ -194,7 +194,7 @@ namespace osu.Game.Skinning.Editor private void save() { - SkinnableElementTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); + SkinnableTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); foreach (var t in targetContainers) currentSkin.Value.UpdateDrawableTarget(t); diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 4890524b90..3a16cb5818 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -65,7 +65,7 @@ namespace osu.Game.Skinning /// Remove all stored customisations for the provided target. /// /// The target container to reset. - public void ResetDrawableTarget(SkinnableElementTargetContainer targetContainer) + public void ResetDrawableTarget(SkinnableTargetContainer targetContainer) { DrawableComponentInfo.Remove(targetContainer.Target); } @@ -74,7 +74,7 @@ namespace osu.Game.Skinning /// Update serialised information for the provided target. /// /// The target container to serialise to this skin. - public void UpdateDrawableTarget(SkinnableElementTargetContainer targetContainer) + public void UpdateDrawableTarget(SkinnableTargetContainer targetContainer) { DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray(); } diff --git a/osu.Game/Skinning/SkinnableElementTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs similarity index 94% rename from osu.Game/Skinning/SkinnableElementTargetContainer.cs rename to osu.Game/Skinning/SkinnableTargetContainer.cs index 2a76d3bf7c..daf54277fd 100644 --- a/osu.Game/Skinning/SkinnableElementTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -11,7 +11,7 @@ using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning { - public class SkinnableElementTargetContainer : SkinReloadableDrawable, ISkinnableTarget + public class SkinnableTargetContainer : SkinReloadableDrawable, ISkinnableTarget { private SkinnableTargetWrapper content; @@ -21,7 +21,7 @@ namespace osu.Game.Skinning private readonly BindableList components = new BindableList(); - public SkinnableElementTargetContainer(SkinnableTarget target) + public SkinnableTargetContainer(SkinnableTarget target) { Target = target; } From 0959e7156a27a29b4a7a8fa041e418e4cc84f577 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 17:22:05 +0900 Subject: [PATCH 606/708] Remove outdated TODO --- osu.Game/Skinning/SkinManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 7b27a7a816..5793edda30 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -180,7 +180,6 @@ namespace osu.Game.Skinning foreach (var drawableInfo in skin.DrawableComponentInfo) { - // todo: the OfType() call can be removed with better IDrawable support. string json = JsonConvert.SerializeObject(drawableInfo.Value, new JsonSerializerSettings { Formatting = Formatting.Indented }); using (var streamContent = new MemoryStream(Encoding.UTF8.GetBytes(json))) From 3ea469813c8cc95396d020a1f145cf0d2c67040e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 17:25:51 +0900 Subject: [PATCH 607/708] Use interface in place of `SkinnableTargetContainer` --- osu.Game/Skinning/ISkinnableTarget.cs | 11 +++++++++++ osu.Game/Skinning/Skin.cs | 4 ++-- osu.Game/Skinning/SkinnableTargetContainer.cs | 9 --------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 3773b5be24..ba3a9fe228 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -1,7 +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.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Extensions; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning { @@ -20,6 +25,12 @@ namespace osu.Game.Skinning /// IBindableList Components { get; } + /// + /// Serialise all children as . + /// + /// The serialised content. + IEnumerable CreateSkinnableInfo() => Components.Select(d => ((Drawable)d).CreateSkinnableInfo()); + /// /// Reload this target from the current skin. /// diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 3a16cb5818..0ceca3925e 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -65,7 +65,7 @@ namespace osu.Game.Skinning /// Remove all stored customisations for the provided target. /// /// The target container to reset. - public void ResetDrawableTarget(SkinnableTargetContainer targetContainer) + public void ResetDrawableTarget(ISkinnableTarget targetContainer) { DrawableComponentInfo.Remove(targetContainer.Target); } @@ -74,7 +74,7 @@ namespace osu.Game.Skinning /// Update serialised information for the provided target. /// /// The target container to serialise to this skin. - public void UpdateDrawableTarget(SkinnableTargetContainer targetContainer) + public void UpdateDrawableTarget(ISkinnableTarget targetContainer) { DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray(); } diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index daf54277fd..52e86f5d86 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -2,12 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Extensions; -using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning { @@ -64,12 +61,6 @@ namespace osu.Game.Skinning components.Add(component); } - /// - /// Serialise all children as . - /// - /// The serialised content. - public IEnumerable CreateSkinnableInfo() => components.Select(d => ((Drawable)d).CreateSkinnableInfo()); - protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); From ebce3fd3c7d408541b39b2fa37df45d82d057a08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 17:29:11 +0900 Subject: [PATCH 608/708] Use `ScheduleAfterChildren` to better match comment --- osu.Game/Skinning/SkinnableTargetWrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableTargetWrapper.cs b/osu.Game/Skinning/SkinnableTargetWrapper.cs index 58ecac71ab..664e8da480 100644 --- a/osu.Game/Skinning/SkinnableTargetWrapper.cs +++ b/osu.Game/Skinning/SkinnableTargetWrapper.cs @@ -40,7 +40,7 @@ namespace osu.Game.Skinning base.LoadComplete(); // schedule is required to allow children to run their LoadComplete and take on their correct sizes. - Schedule(() => applyDefaults?.Invoke(this)); + ScheduleAfterChildren(() => applyDefaults?.Invoke(this)); } } } From 1cda55393ed7b25874c8a3fe7bf77dc33b136035 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 17:51:57 +0900 Subject: [PATCH 609/708] Add aspect ratio locking and flip support to skin editor --- .../Skinning/Editor/SkinSelectionHandler.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index ad783a9c0e..3dc0ca340b 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Game.Extensions; @@ -36,6 +37,20 @@ namespace osu.Game.Skinning.Editor return true; } + public override bool HandleFlip(Direction direction) + { + // TODO: this is temporary as well. + foreach (var c in SelectedBlueprints) + { + ((Drawable)c.Item).Scale *= new Vector2( + direction == Direction.Horizontal ? -1 : 1, + direction == Direction.Vertical ? -1 : 1 + ); + } + + return true; + } + public override bool HandleMovement(MoveSelectionEvent moveEvent) { foreach (var c in SelectedBlueprints) @@ -116,6 +131,15 @@ namespace osu.Game.Skinning.Editor // 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; + + // for now aspect lock scale adjustments that occur at corners. + if (!reference.HasFlagFast(Anchor.x1) && !reference.HasFlagFast(Anchor.y1)) + { + if (reference.HasFlagFast(Anchor.x0) || reference.HasFlagFast(Anchor.x2)) + scale.Y = scale.X; + else + scale.X = scale.Y; + } } public class AnchorMenuItem : TernaryStateMenuItem From 9f8e6979dd90e23bf5b7936bb536eeb312fbb3f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 18:00:25 +0900 Subject: [PATCH 610/708] Fix display of skin blueprints when flipped --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index 58fa255508..1d029c39d6 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -112,8 +112,13 @@ namespace osu.Game.Skinning.Editor drawableQuad = drawable.ScreenSpaceDrawQuad; var quad = ToLocalSpace(drawable.ScreenSpaceDrawQuad); - box.Position = quad.TopLeft; + box.Position = new Vector2( + drawable.Scale.X < 0 ? quad.TopRight.X : quad.TopLeft.X, + drawable.Scale.Y < 0 ? quad.BottomLeft.Y : quad.TopLeft.Y + ); + box.Size = quad.Size; + box.Rotation = drawable.Rotation; } From 9a061ad80b38eea677eed6bb235951c8f035bb6b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 5 May 2021 20:27:28 +0200 Subject: [PATCH 611/708] Extract directory selection logic of migration screen to DirectorySelectScreen. --- .../Maintenance/DirectorySelectScreen.cs | 115 ++++++++++++++++++ .../Maintenance/MigrationSelectScreen.cs | 105 +++------------- 2 files changed, 130 insertions(+), 90 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs new file mode 100644 index 0000000000..278c1ab20d --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -0,0 +1,115 @@ +// 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 System.IO; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Screens; +using osuTK; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Screens; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public abstract class DirectorySelectScreen : OsuScreen + { + private TriangleButton selectionButton; + + protected DirectorySelector DirectorySelector { get; private set; } + + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + + protected abstract OsuSpriteText CreateHeader(); + + /// + /// Called upon selection of a directory by the user. + /// + /// The selected directory + protected abstract void OnSelection(DirectoryInfo directory); + + protected virtual bool IsValidDirectory(DirectoryInfo info) => info != null; + + public override bool AllowExternalScreenChange => false; + + public override bool DisallowExternalBeatmapRulesetChanges => true; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = new Container + { + Masking = true, + CornerRadius = 10, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.5f, 0.8f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDark + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Relative, 0.8f), + new Dimension(), + }, + Content = new[] + { + new Drawable[] + { + CreateHeader().With(header => + { + header.Origin = Anchor.Centre; + header.Anchor = Anchor.Centre; + }) + }, + new Drawable[] + { + DirectorySelector = new DirectorySelector + { + RelativeSizeAxes = Axes.Both, + } + }, + new Drawable[] + { + selectionButton = new TriangleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300, + Text = "Select directory", + Action = () => OnSelection(DirectorySelector.CurrentPath.Value) + }, + } + } + } + } + }; + } + + protected override void LoadComplete() + { + DirectorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = IsValidDirectory(e.NewValue), true); + base.LoadComplete(); + } + + public override void OnSuspending(IScreen next) + { + base.OnSuspending(next); + + this.FadeOut(250); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index ad540e3691..1f14d0991d 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -4,109 +4,28 @@ using System; using System.IO; using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Screens; -using osuTK; namespace osu.Game.Overlays.Settings.Sections.Maintenance { - public class MigrationSelectScreen : OsuScreen + public class MigrationSelectScreen : DirectorySelectScreen { - private DirectorySelector directorySelector; + [Resolved] + private Storage storage { get; set; } - public override bool AllowExternalScreenChange => false; - - public override bool DisallowExternalBeatmapRulesetChanges => true; - - public override bool HideOverlaysOnEnter => true; - - [BackgroundDependencyLoader(true)] - private void load(OsuGame game, Storage storage, OsuColour colours) + protected override OsuSpriteText CreateHeader() => new OsuSpriteText { - game?.Toolbar.Hide(); + Text = "Please select a new location", + Font = OsuFont.Default.With(size: 40) + }; - // begin selection in the parent directory of the current storage location - var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; - - InternalChild = new Container - { - Masking = true, - CornerRadius = 10, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(0.5f, 0.8f), - Children = new Drawable[] - { - new Box - { - Colour = colours.GreySeafoamDark, - RelativeSizeAxes = Axes.Both, - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Relative, 0.8f), - new Dimension(), - }, - Content = new[] - { - new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Please select a new location", - Font = OsuFont.Default.With(size: 40) - }, - }, - new Drawable[] - { - directorySelector = new DirectorySelector(initialPath) - { - RelativeSizeAxes = Axes.Both, - } - }, - new Drawable[] - { - new TriangleButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 300, - Text = "Begin folder migration", - Action = start - }, - } - } - } - } - }; - } - - public override void OnSuspending(IScreen next) + protected override void OnSelection(DirectoryInfo directory) { - base.OnSuspending(next); - - this.FadeOut(250); - } - - private void start() - { - var target = directorySelector.CurrentPath.Value; + var target = directory; try { @@ -123,6 +42,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance BeginMigration(target); } + protected override void LoadComplete() + { + DirectorySelector.CurrentPath.Value = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent; + base.LoadComplete(); + } + protected virtual void BeginMigration(DirectoryInfo target) => this.Push(new MigrationRunScreen(target)); } } From 4bb0e6b7d53bca6188470600b5ca3d6d1b007e65 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 5 May 2021 22:13:25 +0200 Subject: [PATCH 612/708] Create InitialPath property. --- .../Sections/Maintenance/DirectorySelectScreen.cs | 5 +++++ .../Sections/Maintenance/MigrationSelectScreen.cs | 8 ++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index 278c1ab20d..a6ae3d6132 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -34,6 +34,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected virtual bool IsValidDirectory(DirectoryInfo info) => info != null; + protected virtual DirectoryInfo InitialPath => null; + public override bool AllowExternalScreenChange => false; public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -101,6 +103,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override void LoadComplete() { + if (InitialPath != null) + DirectorySelector.CurrentPath.Value = InitialPath; + DirectorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = IsValidDirectory(e.NewValue), true); base.LoadComplete(); } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 1f14d0991d..cfbb7d6593 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -17,6 +17,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [Resolved] private Storage storage { get; set; } + protected override DirectoryInfo InitialPath => new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent; + protected override OsuSpriteText CreateHeader() => new OsuSpriteText { Text = "Please select a new location", @@ -42,12 +44,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance BeginMigration(target); } - protected override void LoadComplete() - { - DirectorySelector.CurrentPath.Value = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent; - base.LoadComplete(); - } - protected virtual void BeginMigration(DirectoryInfo target) => this.Push(new MigrationRunScreen(target)); } } From d3cc418961462f9961ba8551e18671c7046c92ed Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 6 May 2021 11:12:19 +0200 Subject: [PATCH 613/708] Privatize DirectorySelector. --- .../Sections/Maintenance/DirectorySelectScreen.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index a6ae3d6132..c08323e67c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { private TriangleButton selectionButton; - protected DirectorySelector DirectorySelector { get; private set; } + private DirectorySelector directorySelector; protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }, new Drawable[] { - DirectorySelector = new DirectorySelector + directorySelector = new DirectorySelector { RelativeSizeAxes = Axes.Both, } @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Origin = Anchor.Centre, Width = 300, Text = "Select directory", - Action = () => OnSelection(DirectorySelector.CurrentPath.Value) + Action = () => OnSelection(directorySelector.CurrentPath.Value) }, } } @@ -104,9 +104,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override void LoadComplete() { if (InitialPath != null) - DirectorySelector.CurrentPath.Value = InitialPath; + directorySelector.CurrentPath.Value = InitialPath; - DirectorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = IsValidDirectory(e.NewValue), true); + directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = IsValidDirectory(e.NewValue), true); base.LoadComplete(); } From eee3cd7c5770c1e4e42383980ad9b8d65b6381e6 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 11:14:05 +0200 Subject: [PATCH 614/708] Disallow selecting storage root as a valid directory. --- .../Settings/Sections/Maintenance/DirectorySelectScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index c08323e67c..582d14fbb6 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance /// The selected directory protected abstract void OnSelection(DirectoryInfo directory); - protected virtual bool IsValidDirectory(DirectoryInfo info) => info != null; + protected virtual bool IsValidDirectory(DirectoryInfo info) => true; protected virtual DirectoryInfo InitialPath => null; @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (InitialPath != null) directorySelector.CurrentPath.Value = InitialPath; - directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = IsValidDirectory(e.NewValue), true); + directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = e.NewValue != null ? IsValidDirectory(e.NewValue) : false, true); base.LoadComplete(); } From 19800f5f7f22d81bc0784796b78dc811e4a8458b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 11:24:22 +0200 Subject: [PATCH 615/708] Move `IBeatmap` arg into context --- .../Editor/Checks/CheckOffscreenObjectsTest.cs | 12 ++++++------ .../Edit/Checks/CheckOffscreenObjects.cs | 5 ++--- osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs | 5 ++--- .../Editing/Checks/CheckAudioQualityTest.cs | 14 +++++++------- .../Editing/Checks/CheckBackgroundQualityTest.cs | 14 +++++++------- .../Editing/Checks/CheckConcurrentObjectsTest.cs | 15 ++++++++------- .../Editing/Checks/CheckFilePresenceTest.cs | 12 ++++++------ .../Editing/Checks/CheckUnsnappedObjectsTest.cs | 15 ++++++++------- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 5 ++--- osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs | 8 +++++++- .../Rulesets/Edit/Checks/CheckAudioQuality.cs | 5 ++--- .../Edit/Checks/CheckBackgroundQuality.cs | 7 +++---- .../Edit/Checks/CheckConcurrentObjects.cs | 11 +++++------ .../Rulesets/Edit/Checks/CheckFilePresence.cs | 6 +++--- .../Rulesets/Edit/Checks/CheckUnsnappedObjects.cs | 7 +++---- .../Rulesets/Edit/Checks/Components/ICheck.cs | 4 +--- osu.Game/Rulesets/Edit/IBeatmapVerifier.cs | 3 +-- osu.Game/Screens/Edit/Verify/IssueList.cs | 6 +++--- 18 files changed, 76 insertions(+), 78 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index 5545273af2..a6873c6de9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -225,14 +225,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOk(IBeatmap beatmap) { - var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); - Assert.That(check.Run(beatmap, context), Is.Empty); + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + Assert.That(check.Run(context), Is.Empty); } private void assertOffscreenCircle(IBeatmap beatmap) { - var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); - var issues = check.Run(beatmap, context).ToList(); + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle); @@ -240,8 +240,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOffscreenSlider(IBeatmap beatmap) { - var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); - var issues = check.Run(beatmap, context).ToList(); + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + var issues = check.Run(context).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 86bb7f203f..a342c2a821 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Osu.Objects; @@ -32,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks new IssueTemplateOffscreenSlider(this) }; - public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) + public IEnumerable Run(BeatmapVerifierContext context) { - foreach (var hitobject in beatmap.HitObjects) + foreach (var hitobject in context.Beatmap.HitObjects) { switch (hitobject) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index c62f472d75..04e881fbf3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Osu.Edit.Checks; @@ -17,9 +16,9 @@ namespace osu.Game.Rulesets.Osu.Edit new CheckOffscreenObjects() }; - public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) + public IEnumerable Run(BeatmapVerifierContext context) { - return checks.SelectMany(check => check.Run(beatmap, context)); + return checks.SelectMany(check => check.Run(context)); } } } diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs index fbd02ea54e..1cbdc43140 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Editing.Checks mock.SetupGet(w => w.Beatmap).Returns(beatmap); mock.SetupGet(w => w.Track).Returns((Track)null); - Assert.That(check.Run(beatmap, new BeatmapVerifierContext(mock.Object)), Is.Empty); + Assert.That(check.Run(new BeatmapVerifierContext(beatmap, mock.Object)), Is.Empty); } [Test] @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(192); - Assert.That(check.Run(beatmap, context), Is.Empty); + Assert.That(check.Run(context), Is.Empty); } [Test] @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(null); - var issues = check.Run(beatmap, context).ToList(); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate); @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(0); - var issues = check.Run(beatmap, context).ToList(); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate); @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(320); - var issues = check.Run(beatmap, context).ToList(); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooHighBitrate); @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(64); - var issues = check.Run(beatmap, context).ToList(); + var issues = check.Run( context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooLowBitrate); @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Editing.Checks private BeatmapVerifierContext getContext(int? audioBitrate) { - return new BeatmapVerifierContext(getMockWorkingBeatmap(audioBitrate).Object); + return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(audioBitrate).Object); } /// diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index e96ec5485d..3424cfe732 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Editing.Checks beatmap.Metadata.BackgroundFile = null; var context = getContext(null, System.Array.Empty()); - Assert.That(check.Run(beatmap, context), Is.Empty); + Assert.That(check.Run(context), Is.Empty); } [Test] @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(new Texture(1920, 1080)); - Assert.That(check.Run(beatmap, context), Is.Empty); + Assert.That(check.Run(context), Is.Empty); } [Test] @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(new Texture(3840, 2160)); - var issues = check.Run(beatmap, context).ToList(); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooHighResolution); @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(new Texture(640, 480)); - var issues = check.Run(beatmap, context).ToList(); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateLowResolution); @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(new Texture(100, 100)); - var issues = check.Run(beatmap, context).ToList(); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooLowResolution); @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); - var issues = check.Run(beatmap, context).ToList(); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed); @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Editing.Checks private BeatmapVerifierContext getContext(Texture background, [CanBeNull] byte[] fileBytes = null) { - return new BeatmapVerifierContext(getMockWorkingBeatmap(background, fileBytes).Object); + return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, fileBytes).Object); } /// diff --git a/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs index ffe5d34e67..ba0a130a25 100644 --- a/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs @@ -6,6 +6,7 @@ using System.Linq; using Moq; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; @@ -105,7 +106,7 @@ namespace osu.Game.Tests.Editing.Checks new HitCircle { StartTime = 300 } }; - var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + var issues = check.Run(getContext(hitobjects)).ToList(); Assert.That(issues, Has.Count.EqualTo(3)); Assert.That(issues.Where(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentDifferent).ToList(), Has.Count.EqualTo(2)); @@ -164,12 +165,12 @@ namespace osu.Game.Tests.Editing.Checks private void assertOk(List hitobjects) { - Assert.That(check.Run(getPlayableBeatmap(hitobjects), null), Is.Empty); + Assert.That(check.Run(getContext(hitobjects)), Is.Empty); } private void assertConcurrentSame(List hitobjects, int count = 1) { - var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + var issues = check.Run(getContext(hitobjects)).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); Assert.That(issues.All(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentSame)); @@ -177,18 +178,18 @@ namespace osu.Game.Tests.Editing.Checks private void assertConcurrentDifferent(List hitobjects, int count = 1) { - var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + var issues = check.Run(getContext(hitobjects)).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); Assert.That(issues.All(issue => issue.Template is CheckConcurrentObjects.IssueTemplateConcurrentDifferent)); } - private IBeatmap getPlayableBeatmap(List hitobjects) + private BeatmapVerifierContext getContext(List hitobjects) { - return new Beatmap + return new BeatmapVerifierContext(new Beatmap { HitObjects = hitobjects - }; + }, null); } } } diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index 424dffcbc2..39a1d76d83 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -46,8 +46,8 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestBackgroundSetAndInFiles() { - var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); - Assert.That(check.Run(beatmap, context), Is.Empty); + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + Assert.That(check.Run(context), Is.Empty); } [Test] @@ -55,8 +55,8 @@ namespace osu.Game.Tests.Editing.Checks { beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); - var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); - var issues = check.Run(beatmap, context).ToList(); + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateDoesNotExist); @@ -67,8 +67,8 @@ namespace osu.Game.Tests.Editing.Checks { beatmap.Metadata.BackgroundFile = null; - var context = new BeatmapVerifierContext(new TestWorkingBeatmap(beatmap)); - var issues = check.Run(beatmap, context).ToList(); + var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateNoneSet); diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs index 5e65b263f2..02159fa57b 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs @@ -7,6 +7,7 @@ using Moq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -105,7 +106,7 @@ namespace osu.Game.Tests.Editing.Checks getSliderMock(startTime: 98, endTime: 398.75d).Object }; - var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + var issues = check.Run(getContext(hitobjects)).ToList(); Assert.That(issues, Has.Count.EqualTo(2)); Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap)); @@ -124,12 +125,12 @@ namespace osu.Game.Tests.Editing.Checks private void assertOk(List hitobjects) { - Assert.That(check.Run(getPlayableBeatmap(hitobjects), null), Is.Empty); + Assert.That(check.Run(getContext(hitobjects)), Is.Empty); } private void assert1Ms(List hitobjects, int count = 1) { - var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + var issues = check.Run(getContext(hitobjects)).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap)); @@ -137,19 +138,19 @@ namespace osu.Game.Tests.Editing.Checks private void assert2Ms(List hitobjects, int count = 1) { - var issues = check.Run(getPlayableBeatmap(hitobjects), null).ToList(); + var issues = check.Run(getContext(hitobjects)).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateLargeUnsnap)); } - private IBeatmap getPlayableBeatmap(List hitobjects) + private BeatmapVerifierContext getContext(List hitobjects) { - return new Beatmap + return new BeatmapVerifierContext(new Beatmap { ControlPointInfo = cpi, HitObjects = hitobjects - }; + }, null); } } } diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 1860d54b57..d208c7fe07 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Edit.Checks.Components; @@ -29,9 +28,9 @@ namespace osu.Game.Rulesets.Edit new CheckConcurrentObjects() }; - public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) + public IEnumerable Run(BeatmapVerifierContext context) { - return checks.SelectMany(check => check.Run(beatmap, context)); + return checks.SelectMany(check => check.Run(context)); } } } diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs index 76b74cb993..53bdf3140c 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs @@ -11,6 +11,11 @@ namespace osu.Game.Rulesets.Edit /// public class BeatmapVerifierContext { + /// + /// The playable beatmap instance of the current beatmap. + /// + public readonly IBeatmap Beatmap; + /// /// The working beatmap instance of the current beatmap. /// @@ -21,8 +26,9 @@ namespace osu.Game.Rulesets.Edit /// public DifficultyRating InterpretedDifficulty; - public BeatmapVerifierContext(IWorkingBeatmap workingBeatmap, DifficultyRating difficultyRating = DifficultyRating.ExpertPlus) + public BeatmapVerifierContext(IBeatmap beatmap, IWorkingBeatmap workingBeatmap, DifficultyRating difficultyRating = DifficultyRating.ExpertPlus) { + Beatmap = beatmap; WorkingBeatmap = workingBeatmap; InterpretedDifficulty = difficultyRating; } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs index 1015f267aa..70d11883b7 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs @@ -2,7 +2,6 @@ // 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 @@ -26,9 +25,9 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateNoBitrate(this) }; - public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) + public IEnumerable Run(BeatmapVerifierContext context) { - var audioFile = beatmap.Metadata?.AudioFile; + var audioFile = context.Beatmap.Metadata?.AudioFile; if (audioFile == null) yield break; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 87f5c80c89..085c558eaf 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -2,7 +2,6 @@ // 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 @@ -30,9 +29,9 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateTooUncompressed(this) }; - public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) + public IEnumerable Run(BeatmapVerifierContext context) { - var backgroundFile = beatmap.Metadata?.BackgroundFile; + var backgroundFile = context.Beatmap.Metadata?.BackgroundFile; if (backgroundFile == null) yield break; @@ -48,7 +47,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 = beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); + string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); double filesizeMb = context.WorkingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); if (filesizeMb > max_filesize_mb) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs index fd6ed664e6..51277298ab 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -2,7 +2,6 @@ // 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; @@ -22,15 +21,15 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateConcurrentDifferent(this) }; - public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) + public IEnumerable Run(BeatmapVerifierContext context) { - for (int i = 0; i < beatmap.HitObjects.Count - 1; ++i) + for (int i = 0; i < context.Beatmap.HitObjects.Count - 1; ++i) { - var hitobject = beatmap.HitObjects[i]; + var hitobject = context.Beatmap.HitObjects[i]; - for (int j = i + 1; j < beatmap.HitObjects.Count; ++j) + for (int j = i + 1; j < context.Beatmap.HitObjects.Count; ++j) { - var nextHitobject = beatmap.HitObjects[j]; + var nextHitobject = context.Beatmap.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. diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index f04909d175..36a0bf8c5d 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs @@ -21,9 +21,9 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateDoesNotExist(this) }; - public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) + public IEnumerable Run(BeatmapVerifierContext context) { - var filename = GetFilename(beatmap); + var filename = GetFilename(context.Beatmap); if (filename == null) { @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Edit.Checks } // If the file is set, also make sure it still exists. - var storagePath = beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); + var storagePath = context.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); if (storagePath != null) yield break; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs index aa19f3df07..ded1bb54ca 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckUnsnappedObjects.cs @@ -3,7 +3,6 @@ using System; 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; @@ -22,11 +21,11 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateSmallUnsnap(this) }; - public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context) + public IEnumerable Run(BeatmapVerifierContext context) { - var controlPointInfo = beatmap.ControlPointInfo; + var controlPointInfo = context.Beatmap.ControlPointInfo; - foreach (var hitobject in beatmap.HitObjects) + foreach (var hitobject in context.Beatmap.HitObjects) { double startUnsnap = hitobject.StartTime - controlPointInfo.GetClosestSnappedTime(hitobject.StartTime); string startPostfix = hitobject is IHasDuration ? "start" : ""; diff --git a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs index 511d6aaa0f..141de55f1d 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Edit.Checks.Components { @@ -24,8 +23,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// Runs this check and returns any issues detected for the provided beatmap. /// - /// The playable beatmap of the beatmap to run the check on. /// The beatmap verifier context associated with the beatmap. - public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context); + public IEnumerable Run(BeatmapVerifierContext context); } } diff --git a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs index 1dafc6938e..06f0abedb0 100644 --- a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs @@ -2,7 +2,6 @@ // 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 @@ -12,6 +11,6 @@ namespace osu.Game.Rulesets.Edit /// public interface IBeatmapVerifier { - public IEnumerable Run(IBeatmap beatmap, BeatmapVerifierContext context); + public IEnumerable Run(BeatmapVerifierContext context); } } diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index abf22ead10..fc9d4c7526 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Verify generalVerifier = new BeatmapVerifier(); rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); - context = new BeatmapVerifierContext(workingBeatmap.Value, verify.InterpretedDifficulty.Value); + context = new BeatmapVerifierContext(beatmap, workingBeatmap.Value, verify.InterpretedDifficulty.Value); verify.InterpretedDifficulty.BindValueChanged(change => { context.InterpretedDifficulty = change.NewValue; @@ -98,10 +98,10 @@ namespace osu.Game.Screens.Edit.Verify private void refresh() { - var issues = generalVerifier.Run(beatmap, context); + var issues = generalVerifier.Run(context); if (rulesetVerifier != null) - issues = issues.Concat(rulesetVerifier.Run(beatmap, context)); + issues = issues.Concat(rulesetVerifier.Run(context)); issues = filter(issues); From df77b28b48b8a58a92380c458251efcaf73773c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 18:39:21 +0900 Subject: [PATCH 616/708] Add a flimsy guard against null parent to avoid crashes on exit sequence --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index 7db5bc7ead..b337aa53c4 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -172,6 +172,9 @@ namespace osu.Game.Skinning.Editor { base.Update(); + if (drawable.Parent == null) + return; + originBox.Position = drawable.ToSpaceOfOtherDrawable(drawable.OriginPosition, this); anchorBox.Position = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this); From 01bc71acd2a24c3fa5993aab6487f4fdee7d7062 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 18:40:28 +0900 Subject: [PATCH 617/708] Improve ability to parse xmldoc of `SkinnableTargetWrapper` Co-authored-by: Dan Balasescu --- osu.Game/Skinning/SkinnableTargetWrapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableTargetWrapper.cs b/osu.Game/Skinning/SkinnableTargetWrapper.cs index 664e8da480..395de590cf 100644 --- a/osu.Game/Skinning/SkinnableTargetWrapper.cs +++ b/osu.Game/Skinning/SkinnableTargetWrapper.cs @@ -9,8 +9,8 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Skinning { /// - /// A container which is serialised and can encapsulate multiple skinnable elements into a single return type (for consumption via . - /// Will also optionally apply default cross-element layout dependencies when initialised from a non-deserialised source. + /// A container which groups the elements of a into a single object. + /// Optionally also applies a default layout to the elements. /// [Serializable] public class SkinnableTargetWrapper : Container, ISkinnableDrawable From 2f025f196723c0711e1573962515cabb892daae7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 18:51:23 +0900 Subject: [PATCH 618/708] SkinnableTargetWrapper -> SkinnableTargetComponentsContainer --- osu.Game/Skinning/DefaultSkin.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/Skin.cs | 2 +- ...rapper.cs => SkinnableTargetComponentsContainer.cs} | 10 +++++----- osu.Game/Skinning/SkinnableTargetContainer.cs | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) rename osu.Game/Skinning/{SkinnableTargetWrapper.cs => SkinnableTargetComponentsContainer.cs} (73%) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index d6ca5e9009..65e8fd1b82 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -47,7 +47,7 @@ namespace osu.Game.Skinning switch (target.Target) { case SkinnableTarget.MainHUDComponents: - var skinnableTargetWrapper = new SkinnableTargetWrapper(container => + var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 27f6fcdf97..a6f8f45c0f 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -332,7 +332,7 @@ namespace osu.Game.Skinning { case SkinnableTarget.MainHUDComponents: - var skinnableTargetWrapper = new SkinnableTargetWrapper(container => + var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 0ceca3925e..2944c7a8ec 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -87,7 +87,7 @@ namespace osu.Game.Skinning if (!DrawableComponentInfo.TryGetValue(target.Target, out var skinnableInfo)) return null; - return new SkinnableTargetWrapper + return new SkinnableTargetComponentsContainer { ChildrenEnumerable = skinnableInfo.Select(i => i.CreateInstance()) }; diff --git a/osu.Game/Skinning/SkinnableTargetWrapper.cs b/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs similarity index 73% rename from osu.Game/Skinning/SkinnableTargetWrapper.cs rename to osu.Game/Skinning/SkinnableTargetComponentsContainer.cs index 395de590cf..2107ca7a8b 100644 --- a/osu.Game/Skinning/SkinnableTargetWrapper.cs +++ b/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs @@ -9,11 +9,11 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Skinning { /// - /// A container which groups the elements of a into a single object. - /// Optionally also applies a default layout to the elements. + /// A container which groups the components of a into a single object. + /// Optionally also applies a default layout to the components. /// [Serializable] - public class SkinnableTargetWrapper : Container, ISkinnableDrawable + public class SkinnableTargetComponentsContainer : Container, ISkinnableDrawable { public bool IsEditable => false; @@ -23,14 +23,14 @@ namespace osu.Game.Skinning /// Construct a wrapper with defaults that should be applied once. /// /// A function to apply the default layout. - public SkinnableTargetWrapper(Action applyDefaults) + public SkinnableTargetComponentsContainer(Action applyDefaults) : this() { this.applyDefaults = applyDefaults; } [JsonConstructor] - public SkinnableTargetWrapper() + public SkinnableTargetComponentsContainer() { RelativeSizeAxes = Axes.Both; } diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index 52e86f5d86..a4d7f621eb 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -10,7 +10,7 @@ namespace osu.Game.Skinning { public class SkinnableTargetContainer : SkinReloadableDrawable, ISkinnableTarget { - private SkinnableTargetWrapper content; + private SkinnableTargetComponentsContainer content; public SkinnableTarget Target { get; } @@ -31,7 +31,7 @@ namespace osu.Game.Skinning ClearInternal(); components.Clear(); - content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetWrapper; + content = CurrentSkin.GetDrawableComponent(new SkinnableTargetComponent(Target)) as SkinnableTargetComponentsContainer; if (content != null) { From 07e475cd132cde77478a45ac976173bb4abfdf67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 18:54:40 +0900 Subject: [PATCH 619/708] Fix skin blueprint box drawing incorrectly when both scale and rotation are applied --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index 1d029c39d6..b2a1b1c9d8 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.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.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -112,14 +113,10 @@ namespace osu.Game.Skinning.Editor drawableQuad = drawable.ScreenSpaceDrawQuad; var quad = ToLocalSpace(drawable.ScreenSpaceDrawQuad); - box.Position = new Vector2( - drawable.Scale.X < 0 ? quad.TopRight.X : quad.TopLeft.X, - drawable.Scale.Y < 0 ? quad.BottomLeft.Y : quad.TopLeft.Y - ); - + box.Position = drawable.ToSpaceOfOtherDrawable(Vector2.Zero, this); box.Size = quad.Size; - box.Rotation = drawable.Rotation; + box.Scale = new Vector2(MathF.Sign(drawable.Scale.X), MathF.Sign(drawable.Scale.Y)); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); From e5f765d1a8ef42e9a12fe94dbe23f3d464d1972d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 19:06:58 +0900 Subject: [PATCH 620/708] Fix broken exception message --- 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 67285987ef..cb27a84a75 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -166,7 +166,7 @@ namespace osu.Game.Skinning.Editor private void placeComponent(Type type) { if (!(Activator.CreateInstance(type) is ISkinnableDrawable component)) - throw new InvalidOperationException("Attempted to instantiate a component for placement which was not an {typeof(ISkinnableComponent)}."); + throw new InvalidOperationException($"Attempted to instantiate a component for placement which was not an {typeof(ISkinnableDrawable)}."); getTarget(SkinnableTarget.MainHUDComponents)?.Add(component); From ffb6135a1bd0dd531d86ff238c62ed0dfecda5b8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 19:53:32 +0900 Subject: [PATCH 621/708] Rework hitobject blueprints to take in hitobject models --- .../TestSceneHoldNoteSelectionBlueprint.cs | 2 +- .../Editor/TestSceneNoteSelectionBlueprint.cs | 2 +- .../HoldNoteNoteSelectionBlueprint.cs | 5 +++-- .../Blueprints/HoldNoteSelectionBlueprint.cs | 9 ++++---- .../Blueprints/ManiaSelectionBlueprint.cs | 9 ++++---- .../Edit/Blueprints/NoteSelectionBlueprint.cs | 6 ++--- .../Edit/ManiaBlueprintContainer.cs | 11 +++++----- .../Edit/ManiaSelectionHandler.cs | 5 ++--- .../TestSceneHitCircleSelectionBlueprint.cs | 6 ++--- .../TestSceneSliderControlPointPiece.cs | 8 +++---- .../TestSceneSliderSelectionBlueprint.cs | 8 +++---- .../TestSceneSpinnerSelectionBlueprint.cs | 2 +- .../HitCircles/HitCircleSelectionBlueprint.cs | 4 ++-- .../Edit/Blueprints/OsuSelectionBlueprint.cs | 10 ++++----- .../Sliders/SliderCircleSelectionBlueprint.cs | 3 +-- .../Sliders/SliderSelectionBlueprint.cs | 16 +++++--------- .../Spinners/SpinnerSelectionBlueprint.cs | 3 +-- .../Edit/OsuBlueprintContainer.cs | 13 +++++------ .../Blueprints/TaikoSelectionBlueprint.cs | 6 ++--- .../Edit/TaikoBlueprintContainer.cs | 3 +-- ...rint.cs => HitObjectSelectionBlueprint.cs} | 22 ++++++++++++++----- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 12 +++++----- .../Components/ComposeBlueprintContainer.cs | 5 ++--- .../Compose/Components/MoveSelectionEvent.cs | 2 +- .../Visual/SelectionBlueprintTestScene.cs | 4 +++- 25 files changed, 90 insertions(+), 86 deletions(-) rename osu.Game/Rulesets/Edit/{OverlaySelectionBlueprint.cs => HitObjectSelectionBlueprint.cs} (68%) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs index 24f4c6858e..5e99264d7d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor } }; - AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject)); + AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableObject); } protected override void Update() diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs index 0e47a12a8e..9c3ad0b4ff 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor Child = drawableObject = new DrawableNote(note) }; - AddBlueprint(new NoteSelectionBlueprint(drawableObject)); + AddBlueprint(new NoteSelectionBlueprint(note), drawableObject); } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs index 4e73883de0..14c8d488a9 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs @@ -3,17 +3,18 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint + public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint { protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; private readonly HoldNotePosition position; - public HoldNoteNoteSelectionBlueprint(DrawableHoldNote holdNote, HoldNotePosition position) + public HoldNoteNoteSelectionBlueprint(HoldNote holdNote, HoldNotePosition position) : base(holdNote) { this.position = position; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 1737c4d2e5..c7ee6097c6 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -8,13 +8,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint + public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint { public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; @@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private OsuColour colours { get; set; } - public HoldNoteSelectionBlueprint(DrawableHoldNote hold) + public HoldNoteSelectionBlueprint(HoldNote hold) : base(hold) { } @@ -40,8 +41,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints InternalChildren = new Drawable[] { - new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start), - new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End), + new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.Start), + new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.End), new Container { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 384f49d9b2..e744bd3c83 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -4,22 +4,23 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint + public abstract class ManiaSelectionBlueprint : HitObjectSelectionBlueprint + where T : ManiaHitObject { public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject; [Resolved] private IScrollingInfo scrollingInfo { get; set; } - protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) - : base(drawableObject) + protected ManiaSelectionBlueprint(T hitObject) + : base(hitObject) { RelativeSizeAxes = Axes.None; } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index 2bff33c4cf..e2b6ee0048 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -3,13 +3,13 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; -using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Objects; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class NoteSelectionBlueprint : ManiaSelectionBlueprint + public class NoteSelectionBlueprint : ManiaSelectionBlueprint { - public NoteSelectionBlueprint(DrawableNote note) + public NoteSelectionBlueprint(Note note) : base(note) { AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X }); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs index c4429176d1..c5a109a6d1 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -3,9 +3,8 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; -using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Mania.Edit @@ -17,18 +16,18 @@ namespace osu.Game.Rulesets.Mania.Edit { } - public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) + public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) { switch (hitObject) { - case DrawableNote note: + case Note note: return new NoteSelectionBlueprint(note); - case DrawableHoldNote holdNote: + case HoldNote holdNote: return new HoldNoteSelectionBlueprint(holdNote); } - return base.CreateBlueprintFor(hitObject); + return base.CreateHitObjectBlueprintFor(hitObject); } 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 7042110423..8a1446db32 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI.Scrolling; @@ -23,8 +22,8 @@ namespace osu.Game.Rulesets.Mania.Edit public override bool HandleMovement(MoveSelectionEvent moveEvent) { - var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; - int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; + var maniaBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint; + int lastColumn = maniaBlueprint.HitObject.Column; performColumnMovement(lastColumn, moveEvent); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs index 66cd405195..315493318d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); Add(drawableObject = new DrawableHitCircle(hitCircle)); - AddBlueprint(blueprint = new TestBlueprint(drawableObject)); + AddBlueprint(blueprint = new TestBlueprint(hitCircle), drawableObject); }); [Test] @@ -63,8 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestBlueprint(DrawableHitCircle drawableCircle) - : base(drawableCircle) + public TestBlueprint(HitCircle circle) + : base(circle) { } } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index d7dfc3bd42..fe0f2f8a87 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); Add(drawableObject = new DrawableSlider(slider)); - AddBlueprint(new TestSliderBlueprint(drawableObject)); + AddBlueprint(new TestSliderBlueprint(slider), drawableObject); }); [Test] @@ -154,19 +154,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; - public TestSliderBlueprint(DrawableSlider slider) + public TestSliderBlueprint(Slider slider) : base(slider) { } - protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); + protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); } private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position) + public TestSliderCircleBlueprint(Slider slider, SliderPosition position) : base(slider, position) { } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index f6e1be693b..721bb1985d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); Add(drawableObject = new DrawableSlider(slider)); - AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject)); + AddBlueprint(blueprint = new TestSliderBlueprint(slider), drawableObject); }); [Test] @@ -199,19 +199,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; - public TestSliderBlueprint(DrawableSlider slider) + public TestSliderBlueprint(Slider slider) : base(slider) { } - protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); + protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); } private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position) + public TestSliderCircleBlueprint(Slider slider, SliderPosition position) : base(slider, position) { } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs index 4248f68a60..5007841805 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor Child = drawableSpinner = new DrawableSpinner(spinner) }); - AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) }); + AddBlueprint(new SpinnerSelectionBlueprint(spinner) { Size = new Vector2(0.5f) }, drawableSpinner); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs index abbb54e3c1..b21a3e038e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles protected readonly HitCirclePiece CirclePiece; - public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle) - : base(drawableCircle) + public HitCircleSelectionBlueprint(HitCircle circle) + : base(circle) { InternalChild = CirclePiece = new HitCirclePiece(); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index 299f8fc43a..994c5cebeb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -2,20 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Edit.Blueprints { - public abstract class OsuSelectionBlueprint : OverlaySelectionBlueprint + public abstract class OsuSelectionBlueprint : HitObjectSelectionBlueprint where T : OsuHitObject { - protected T HitObject => (T)DrawableObject.HitObject; + protected new DrawableOsuHitObject DrawableObject => (DrawableOsuHitObject)base.DrawableObject; protected override bool AlwaysShowWhenSelected => true; - protected OsuSelectionBlueprint(DrawableHitObject drawableObject) - : base(drawableObject) + protected OsuSelectionBlueprint(T hitObject) + : base(hitObject) { } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs index dec9cd8622..ff01c7a442 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -3,7 +3,6 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { @@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private readonly SliderPosition position; - public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) + public SliderCircleSelectionBlueprint(Slider slider, SliderPosition position) : base(slider) { this.position = position; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 88fcb1e715..939f4cdc3e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -16,7 +16,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -33,8 +32,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [CanBeNull] protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } - private readonly DrawableSlider slider; - [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } @@ -52,10 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private readonly BindableList controlPoints = new BindableList(); private readonly IBindable pathVersion = new Bindable(); - public SliderSelectionBlueprint(DrawableSlider slider) + public SliderSelectionBlueprint(Slider slider) : base(slider) { - this.slider = slider; } [BackgroundDependencyLoader] @@ -64,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders InternalChildren = new Drawable[] { BodyPiece = new SliderBodyPiece(), - HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), - TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), + HeadBlueprint = CreateCircleSelectionBlueprint(HitObject, SliderPosition.Start), + TailBlueprint = CreateCircleSelectionBlueprint(HitObject, SliderPosition.End), }; } @@ -103,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override void OnSelected() { - AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(slider.HitObject, true) + AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true) { RemoveControlPointsRequested = removeControlPoints }); @@ -215,7 +211,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted - if (controlPoints.Count <= 1 || !slider.HitObject.Path.HasValidLength) + if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength) { placementHandler?.Delete(HitObject); return; @@ -245,6 +241,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; - protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); + protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs index f05d4f8435..ee573d1a01 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs @@ -3,7 +3,6 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners { private readonly SpinnerPiece piece; - public SpinnerSelectionBlueprint(DrawableSpinner spinner) + public SpinnerSelectionBlueprint(Spinner spinner) : base(spinner) { InternalChild = piece = new SpinnerPiece(); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index abac5eb56e..dc8c3d6107 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -3,11 +3,10 @@ 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; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; -using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Osu.Edit @@ -21,21 +20,21 @@ namespace osu.Game.Rulesets.Osu.Edit protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); - public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) + public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) { switch (hitObject) { - case DrawableHitCircle circle: + case HitCircle circle: return new HitCircleSelectionBlueprint(circle); - case DrawableSlider slider: + case Slider slider: return new SliderSelectionBlueprint(slider); - case DrawableSpinner spinner: + case Spinner spinner: return new SpinnerSelectionBlueprint(spinner); } - return base.CreateBlueprintFor(hitObject); + return base.CreateHitObjectBlueprintFor(hitObject); } } } diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs index 62f69122cc..01b90c4bca 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs @@ -3,14 +3,14 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { - public class TaikoSelectionBlueprint : OverlaySelectionBlueprint + public class TaikoSelectionBlueprint : HitObjectSelectionBlueprint { - public TaikoSelectionBlueprint(DrawableHitObject hitObject) + public TaikoSelectionBlueprint(HitObject hitObject) : base(hitObject) { RelativeSizeAxes = Axes.None; diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs index b1b08a9461..a465638779 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -3,7 +3,6 @@ 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; @@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Edit protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); - public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => + public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => new TaikoSelectionBlueprint(hitObject); } } diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs similarity index 68% rename from osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs rename to osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs index 7911cf874b..56434b1d82 100644 --- a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs @@ -9,12 +9,12 @@ using osuTK; namespace osu.Game.Rulesets.Edit { - public abstract class OverlaySelectionBlueprint : SelectionBlueprint + public abstract class HitObjectSelectionBlueprint : SelectionBlueprint { /// - /// The which this applies to. + /// The which this applies to. /// - public readonly DrawableHitObject DrawableObject; + public DrawableHitObject DrawableObject { get; internal set; } /// /// Whether the blueprint should be shown even when the is not alive. @@ -23,10 +23,9 @@ namespace osu.Game.Rulesets.Edit protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); - protected OverlaySelectionBlueprint(DrawableHitObject drawableObject) - : base(drawableObject.HitObject) + protected HitObjectSelectionBlueprint(HitObject hitObject) + : base(hitObject) { - DrawableObject = drawableObject; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); @@ -35,4 +34,15 @@ namespace osu.Game.Rulesets.Edit public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; } + + public abstract class HitObjectSelectionBlueprint : HitObjectSelectionBlueprint + where T : HitObject + { + public T HitObject => (T)Item; + + protected HitObjectSelectionBlueprint(T item) + : base(item) + { + } + } } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 55703a2cd3..f59ad8bfa2 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -105,34 +105,34 @@ namespace osu.Game.Rulesets.Edit protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; /// - /// Selects this , causing it to become visible. + /// Selects this , causing it to become visible. /// public void Select() => State = SelectionState.Selected; /// - /// Deselects this , causing it to become invisible. + /// Deselects this , causing it to become invisible. /// public void Deselect() => State = SelectionState.NotSelected; /// - /// Toggles the selection state of this . + /// Toggles the selection state of this . /// public void ToggleSelection() => State = IsSelected ? SelectionState.NotSelected : SelectionState.Selected; public bool IsSelected => State == SelectionState.Selected; /// - /// The s to be displayed in the context menu for this . + /// The s to be displayed in the context menu for this . /// public virtual MenuItem[] ContextMenuItems => Array.Empty(); /// - /// The screen-space point that causes this to be selected via a drag. + /// The screen-space point that causes this to be selected via a drag. /// public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre; /// - /// The screen-space quad that outlines this for selections. + /// The screen-space quad that outlines this for selections. /// public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 6c174e563e..95f4069edb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -16,7 +16,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.TernaryButtons; using osuTK; @@ -246,10 +245,10 @@ namespace osu.Game.Screens.Edit.Compose.Components if (drawable == null) return null; - return CreateBlueprintFor(drawable); + return CreateHitObjectBlueprintFor(item).With(b => b.DrawableObject = drawable); } - public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; + public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; protected override void OnBlueprintAdded(HitObject item) { diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index 4d4f4b76c6..e32dcc81ee 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// An event which occurs when a is moved. + /// An event which occurs when a is moved. /// public class MoveSelectionEvent { diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index 1176361679..dc12a4999d 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Tests.Visual { @@ -22,10 +23,11 @@ namespace osu.Game.Tests.Visual }); } - protected void AddBlueprint(OverlaySelectionBlueprint blueprint) + protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, DrawableHitObject drawableObject) { Add(blueprint.With(d => { + d.DrawableObject = drawableObject; d.Depth = float.MinValue; d.Select(); })); From 94538b38429f5d7a2447c44e0617f7c868e44437 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 13 May 2021 12:56:36 +0200 Subject: [PATCH 622/708] Remove accidental whitespace --- osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs index 1cbdc43140..39fbf11d51 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Editing.Checks { var context = getContext(64); - var issues = check.Run( context).ToList(); + var issues = check.Run(context).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooLowBitrate); From 2307889bf81762d647718a17d0d4e326782dd4b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 21:41:06 +0900 Subject: [PATCH 623/708] Fix incorrect cast --- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 8a1446db32..a0a43ed6ca 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Mania.Edit public override bool HandleMovement(MoveSelectionEvent moveEvent) { - var maniaBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint; - int lastColumn = maniaBlueprint.HitObject.Column; + var hitObjectBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint; + int lastColumn = ((ManiaHitObject)hitObjectBlueprint.Item).Column; performColumnMovement(lastColumn, moveEvent); From 1e23c535070102c326ca609f56b0d68765bcc25f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 21:59:38 +0900 Subject: [PATCH 624/708] Fix inspection --- osu.Game/Skinning/SkinnableDrawable.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 77f7cf5c3f..fc2730ca44 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -23,7 +23,7 @@ namespace osu.Game.Skinning /// Whether the drawable component should be centered in available space. /// Defaults to true. /// - public bool CentreComponent { get; set; } = true; + public bool CentreComponent = true; public new Axes AutoSizeAxes { @@ -42,7 +42,8 @@ namespace osu.Game.Skinning /// A function to create the default skin implementation of this element. /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. /// How (if at all) the should be resize to fit within our own bounds. - public SkinnableDrawable(ISkinComponent component, Func defaultImplementation = null, Func allowFallback = null, ConfineMode confineMode = ConfineMode.NoScaling) + public SkinnableDrawable(ISkinComponent component, Func defaultImplementation = null, Func allowFallback = null, + ConfineMode confineMode = ConfineMode.NoScaling) : this(component, allowFallback, confineMode) { createDefault = defaultImplementation; From 4cf4817ad2abc61de9f754b7cd9e714bd19b74e0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 22:11:58 +0900 Subject: [PATCH 625/708] Remove redundant parens --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index f7eec4d661..79dd45c07c 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -124,7 +124,7 @@ namespace osu.Game.Skinning.Editor { var drawable = (Drawable)item; - var previousAnchor = (drawable.AnchorPosition); + var previousAnchor = drawable.AnchorPosition; drawable.Anchor = anchor; drawable.Position -= drawable.AnchorPosition - previousAnchor; } From 6c12cae105a98f0d313d16f00b2cdfa9c4960bfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 22:25:11 +0900 Subject: [PATCH 626/708] Remove unnecessary property --- osu.Game/Skinning/Editor/SkinBlueprint.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinBlueprint.cs b/osu.Game/Skinning/Editor/SkinBlueprint.cs index f7509d7b8f..7a5547296b 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprint.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprint.cs @@ -23,11 +23,6 @@ namespace osu.Game.Skinning.Editor private Drawable drawable => (Drawable)Item; - /// - /// Whether the blueprint should be shown even when the is not alive. - /// - protected virtual bool AlwaysShowWhenSelected => true; - protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent; [Resolved] From 09a5b9c872eaee74470f9e3cbe9c3e789715e882 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 16:28:03 +0200 Subject: [PATCH 627/708] Add XMLDoc to protected members. --- .../Sections/Maintenance/DirectorySelectScreen.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index 582d14fbb6..c676e1391d 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -32,8 +32,16 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance /// The selected directory protected abstract void OnSelection(DirectoryInfo directory); + /// + /// Whether the current directory is considered to be valid and can be selected. + /// + /// The current directory. + /// Whether the selected directory is considered valid. protected virtual bool IsValidDirectory(DirectoryInfo info) => true; + /// + /// The path at which to start selection from. + /// protected virtual DirectoryInfo InitialPath => null; public override bool AllowExternalScreenChange => false; From 25b1443c5011fb7c5e3144e05159a92434206cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 May 2021 17:50:12 +0200 Subject: [PATCH 628/708] Remove dead branch and mark implementation as temporary The previous implementation was checking if the `x0` or `x2` anchors were selected to decide on which way to transfer the drawable's scale, but that check actually ends up being always true for corner anchors. To visualise, this is how the corner anchors correspond to `Anchor` flags: x0 x1 x2 | | | y0 -O---O---O- | | | y1 -O---+---O- | | | y2 -O---O---O- | | | The Os indicate where the reference anchors are on a selection box. The first conditional eliminates the middle ones, which makes sense. But after excluding them from further deliberations (marking via X): x0 x1 x2 | | | y0 -O---X---O- | | | y1 -X---+---X- | | | y2 -O---X---O- | | | The remaining anchors always have `x0` or `x2` set. So to avoid confusion, just always transfer one way for now. At some point this should be torn out in favour of an actual implementation of the desired behaviour. --- osu.Game/Skinning/Editor/SkinSelectionHandler.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 485cd3fe93..9bcdc6e08b 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -158,10 +158,8 @@ namespace osu.Game.Skinning.Editor // for now aspect lock scale adjustments that occur at corners. if (!reference.HasFlagFast(Anchor.x1) && !reference.HasFlagFast(Anchor.y1)) { - if (reference.HasFlagFast(Anchor.x0) || reference.HasFlagFast(Anchor.x2)) - scale.Y = scale.X; - else - scale.X = scale.Y; + // TODO: temporary implementation - only dragging the corner handles across the X axis changes size. + scale.Y = scale.X; } } From 0caba57945ba161274132260772580b8f747e9d2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 19:28:23 +0200 Subject: [PATCH 629/708] Make screen properties local to MigrationSelectScreen. --- .../Settings/Sections/Maintenance/DirectorySelectScreen.cs | 6 ------ .../Settings/Sections/Maintenance/MigrationSelectScreen.cs | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index c676e1391d..6e6df14a92 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -22,8 +22,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private DirectorySelector directorySelector; - protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; - protected abstract OsuSpriteText CreateHeader(); /// @@ -44,10 +42,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance /// protected virtual DirectoryInfo InitialPath => null; - public override bool AllowExternalScreenChange => false; - - public override bool DisallowExternalBeatmapRulesetChanges => true; - [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index cfbb7d6593..6334bffe94 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -19,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override DirectoryInfo InitialPath => new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent; + public override bool AllowExternalScreenChange => false; + + public override bool DisallowExternalBeatmapRulesetChanges => true; + + public override bool HideOverlaysOnEnter => true; + protected override OsuSpriteText CreateHeader() => new OsuSpriteText { Text = "Please select a new location", From 6f248db519146eace1face0eb55c96d4c6b3784c Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 13 May 2021 19:31:10 +0200 Subject: [PATCH 630/708] Merge conditional expression. --- .../Settings/Sections/Maintenance/DirectorySelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index 6e6df14a92..c2366ca209 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -108,7 +108,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (InitialPath != null) directorySelector.CurrentPath.Value = InitialPath; - directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = e.NewValue != null ? IsValidDirectory(e.NewValue) : false, true); + directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = e.NewValue != null && IsValidDirectory(e.NewValue), true); base.LoadComplete(); } From ddceafb1b041f0360446e251f9a4755f42f114d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 09:38:25 +0900 Subject: [PATCH 631/708] 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 8c24df5c2e..90d131b117 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 3eb5050e1a..587bdaf622 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 f00e20a66e..7ba7a554d6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 9e8c0a7e7037e6fa118f8d1b1c2c868ca25c286d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 14 May 2021 04:25:29 +0300 Subject: [PATCH 632/708] Fix online play subscreens not pushing player loaders when starting gameplay --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 3 ++- .../Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index fa18b792c3..783b8b4bf2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -27,6 +27,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osuTK; @@ -452,7 +453,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return new MultiSpectatorScreen(userIds); default: - return new MultiplayerPlayer(SelectedItem.Value, userIds); + return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, userIds)); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 11bc55823f..26ee21a2c3 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -13,6 +13,7 @@ using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osuTK; @@ -271,9 +272,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, true); } - protected override Screen CreateGameplayScreen() => new PlaylistsPlayer(SelectedItem.Value) + protected override Screen CreateGameplayScreen() => new PlayerLoader(() => new PlaylistsPlayer(SelectedItem.Value) { Exited = () => leaderboard.RefreshScores() - }; + }); } } From 90e0b3374e2a975e2df4e99cffebb520653d12c9 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Fri, 14 May 2021 03:34:50 +0200 Subject: [PATCH 633/708] Add `#nullable enable` Co-authored-by: Dean Herbert --- osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs index 53bdf3140c..13a892def4 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; - + #nullable enable namespace osu.Game.Rulesets.Edit { /// From a447f200958977b86129d5159edb9891fc6e2031 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Fri, 14 May 2021 03:38:35 +0200 Subject: [PATCH 634/708] Fix formatting of `#nullable enable` --- osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs index 13a892def4..6feee82bda 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifierContext.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; - #nullable enable + +#nullable enable + namespace osu.Game.Rulesets.Edit { /// From 447371478ea6f4843b57a409ebbfab1db1d15e78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 12:03:06 +0900 Subject: [PATCH 635/708] Switch null assignment to non-nullable warnings on --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index aa8f8739c1..4ac796ccd0 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -18,7 +18,7 @@ WARNING HINT DO_NOT_SHOW - HINT + WARNING WARNING WARNING WARNING From b36c991ba1ca714d9b47e34e705fba071e477e22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 12:04:38 +0900 Subject: [PATCH 636/708] Fix single case of incorrect usage --- osu.Game/Screens/Edit/Editor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 434683a016..5ac3401720 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -635,6 +635,9 @@ namespace osu.Game.Screens.Edit case EditorScreenMode.Verify: currentScreen = new VerifyScreen(); break; + + default: + throw new InvalidOperationException("Editor menu bar switched to an unsupported mode"); } LoadComponentAsync(currentScreen, newScreen => From 46e7d9e0edbc18a48e93db72e616a7af67482c75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 12:15:56 +0900 Subject: [PATCH 637/708] Randomise the values displayed in the skinning toolbox To stop the spam of "WYSI" comments everywhere. I guess I underestimated the negative effect this would have. --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 8536cba139..59420bfc87 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -29,8 +30,8 @@ namespace osu.Game.Skinning.Editor [Cached] private ScoreProcessor scoreProcessor = new ScoreProcessor { - Combo = { Value = 727 }, - TotalScore = { Value = 1337377 } + Combo = { Value = RNG.Next(1, 1000) }, + TotalScore = { Value = RNG.Next(1000, 10000000) } }; [Cached(typeof(HealthProcessor))] From 9860e482afe591d5b68916fe594b77ff913ef85e Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Fri, 14 May 2021 05:32:52 +0200 Subject: [PATCH 638/708] Use `TestWorkingBeatmap` instead of null in tests Fixes the warning that #12801 will give. --- .../Editing/Checks/CheckConcurrentObjectsTest.cs | 7 +++---- osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs | 7 +++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs index ba0a130a25..5adb91a22e 100644 --- a/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckConcurrentObjectsTest.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Editing.Checks { @@ -186,10 +187,8 @@ namespace osu.Game.Tests.Editing.Checks private BeatmapVerifierContext getContext(List hitobjects) { - return new BeatmapVerifierContext(new Beatmap - { - HitObjects = hitobjects - }, null); + var beatmap = new Beatmap { HitObjects = hitobjects }; + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); } } } diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs index 02159fa57b..639f5b5ab4 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Editing.Checks { @@ -146,11 +147,13 @@ namespace osu.Game.Tests.Editing.Checks private BeatmapVerifierContext getContext(List hitobjects) { - return new BeatmapVerifierContext(new Beatmap + var beatmap = new Beatmap { ControlPointInfo = cpi, HitObjects = hitobjects - }, null); + }; + + return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); } } } From 48672f8afd156556f29628b3ae246013f72c297d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 15:02:36 +0900 Subject: [PATCH 639/708] Add very basic test logic to ensure `PlayerLoader` is present for playlists --- .../TestScenePlaylistsRoomSubScreen.cs | 20 +++++++++++++++++-- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 6 +++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 319c2bc6fd..264004b6c3 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -11,11 +11,13 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; using osu.Game.Users; using osuTK.Input; @@ -24,8 +26,6 @@ namespace osu.Game.Tests.Visual.Playlists { public class TestScenePlaylistsRoomSubScreen : RoomTestScene { - protected override bool UseOnlineAPI => true; - [Cached(typeof(IRoomManager))] private readonly TestRoomManager roomManager = new TestRoomManager(); @@ -41,6 +41,18 @@ namespace osu.Game.Tests.Visual.Playlists Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait(); + + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case CreateRoomScoreRequest createRoomScoreRequest: + createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 }); + return true; + } + + return false; + }; } [SetUpSteps] @@ -59,12 +71,16 @@ namespace osu.Game.Tests.Visual.Playlists Room.Name.Value = "my awesome room"; Room.Host.Value = new User { Id = 2, Username = "peppy" }; Room.RecentParticipants.Add(Room.Host.Value); + Room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo } }); }); + + AddStep("start match", () => match.ChildrenOfType().First().Click()); + AddUntilStep("player loader loaded", () => Stack.CurrentScreen is PlayerLoader); } [Test] diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 68bdd9160e..28156aecf3 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -150,7 +150,11 @@ namespace osu.Game.Screens.OnlinePlay.Match protected void StartPlay() { sampleStart?.Play(); - ParentScreen?.Push(CreateGameplayScreen()); + + // fallback is to allow this class to operate when there is no parent OnlineScreen (testing purposes). + var targetScreen = (Screen)ParentScreen ?? this; + + targetScreen?.Push(CreateGameplayScreen()); } /// From dc576c19b47fa64e74bfb9fd440dfccf345bd195 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 15:10:02 +0900 Subject: [PATCH 640/708] Fix a potential nullref when starting `Player` with autoplay enabled and beatmap fails to load --- osu.Game/Screens/Play/Player.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ac0c921ccf..15983d1d62 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -154,6 +154,10 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); + // BDL load may have aborted early. + if (DrawableRuleset == null) + return; + // replays should never be recorded or played back when autoplay is enabled if (!Mods.Value.Any(m => m is ModAutoplay)) PrepareReplay(); From 8338f702c3b260dbd01e8dac1b4c19ce01364fb6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 14 May 2021 09:32:56 +0300 Subject: [PATCH 641/708] Remove not required null conditional --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 28156aecf3..375aac729d 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.OnlinePlay.Match // fallback is to allow this class to operate when there is no parent OnlineScreen (testing purposes). var targetScreen = (Screen)ParentScreen ?? this; - targetScreen?.Push(CreateGameplayScreen()); + targetScreen.Push(CreateGameplayScreen()); } /// From 32ff40628973ff325c213cd51a32f2324b722df6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 15:40:29 +0900 Subject: [PATCH 642/708] Add database tracking of beatmap creator `user_id`s --- ...BeatmapManager_BeatmapOnlineLookupQueue.cs | 26 +- osu.Game/Beatmaps/BeatmapMetadata.cs | 21 +- ...9_AddAuthorIdToBeatmapMetadata.Designer.cs | 511 ++++++++++++++++++ ...0514062639_AddAuthorIdToBeatmapMetadata.cs | 23 + .../Migrations/OsuDbContextModelSnapshot.cs | 3 + 5 files changed, 570 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs create mode 100644 osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 7c4b344c9e..5dff4fe282 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading; @@ -83,6 +82,12 @@ namespace osu.Game.Beatmaps beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; beatmap.OnlineBeatmapID = res.OnlineBeatmapID; + if (beatmap.Metadata != null) + beatmap.Metadata.AuthorID = res.AuthorID; + + if (beatmap.BeatmapSet.Metadata != null) + beatmap.BeatmapSet.Metadata.AuthorID = res.AuthorID; + LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); } } @@ -157,7 +162,7 @@ namespace osu.Game.Beatmaps using (var cmd = db.CreateCommand()) { - cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path"; + cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmap.MD5Hash)); cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmap.OnlineBeatmapID ?? (object)DBNull.Value)); @@ -174,6 +179,12 @@ namespace osu.Game.Beatmaps beatmap.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0); beatmap.OnlineBeatmapID = reader.GetInt32(1); + if (beatmap.Metadata != null) + beatmap.Metadata.AuthorID = reader.GetInt32(3); + + if (beatmap.BeatmapSet.Metadata != null) + beatmap.BeatmapSet.Metadata.AuthorID = reader.GetInt32(3); + LogForModel(set, $"Cached local retrieval for {beatmap}."); return true; } @@ -194,17 +205,6 @@ namespace osu.Game.Beatmaps cacheDownloadRequest?.Dispose(); updateScheduler?.Dispose(); } - - [Serializable] - [SuppressMessage("ReSharper", "InconsistentNaming")] - private class CachedOnlineBeatmapLookup - { - public int approved { get; set; } - - public int? beatmapset_id { get; set; } - - public int? beatmap_id { get; set; } - } } } } diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 858da8e602..9540a216fc 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -34,6 +34,21 @@ namespace osu.Game.Beatmaps [JsonIgnore] public List BeatmapSets { get; set; } + /// + /// Helper property to deserialize a username to . + /// + [JsonProperty(@"user_id")] + [Column("AuthorID")] + public int AuthorID + { + get => Author?.Id ?? 1; + set + { + Author ??= new User(); + Author.Id = value; + } + } + /// /// Helper property to deserialize a username to . /// @@ -42,7 +57,11 @@ namespace osu.Game.Beatmaps public string AuthorString { get => Author?.Username; - set => Author = new User { Username = value }; + set + { + Author ??= new User(); + Author.Username = value; + } } /// diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs new file mode 100644 index 0000000000..89bab3a0fa --- /dev/null +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs @@ -0,0 +1,511 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210514062639_AddAuthorIdToBeatmapMetadata")] + partial class AddAuthorIdToBeatmapMetadata + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("EpilepsyWarning"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorID") + .HasColumnName("AuthorID"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs new file mode 100644 index 0000000000..98fe9b5e13 --- /dev/null +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddAuthorIdToBeatmapMetadata : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AuthorID", + table: "BeatmapMetadata", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AuthorID", + table: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index d4bde50b60..f518cfb42b 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -126,6 +126,9 @@ namespace osu.Game.Migrations b.Property("AudioFile"); + b.Property("AuthorID") + .HasColumnName("AuthorID"); + b.Property("AuthorString") .HasColumnName("Author"); From d09da02673186d308e95da4482a06dade52a2a2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 16:03:22 +0900 Subject: [PATCH 643/708] Fix deleting skin elements not saving out to skin Closes https://github.com/ppy/osu/issues/12786. --- osu.Game/Skinning/Editor/SkinEditor.cs | 15 +++++++++++--- .../Skinning/Editor/SkinSelectionHandler.cs | 14 ++++++------- osu.Game/Skinning/ISkinnableTarget.cs | 9 ++++++++- osu.Game/Skinning/SkinnableTargetContainer.cs | 20 +++++++++++++++---- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 6427d6298b..c594f8d271 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.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; @@ -193,14 +194,16 @@ namespace osu.Game.Skinning.Editor SelectedComponents.Add(component); } + private IEnumerable availableTargets => targetScreen.ChildrenOfType(); + private ISkinnableTarget getTarget(SkinnableTarget target) { - return targetScreen.ChildrenOfType().FirstOrDefault(c => c.Target == target); + return availableTargets.FirstOrDefault(c => c.Target == target); } private void revert() { - SkinnableTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); + ISkinnableTarget[] targetContainers = availableTargets.ToArray(); foreach (var t in targetContainers) { @@ -216,7 +219,7 @@ namespace osu.Game.Skinning.Editor if (!hasBegunMutating) return; - SkinnableTargetContainer[] targetContainers = targetScreen.ChildrenOfType().ToArray(); + ISkinnableTarget[] targetContainers = availableTargets.ToArray(); foreach (var t in targetContainers) currentSkin.Value.UpdateDrawableTarget(t); @@ -237,5 +240,11 @@ namespace osu.Game.Skinning.Editor { this.FadeOut(TRANSITION_DURATION, Easing.OutQuint); } + + public void DeleteItems(ISkinnableDrawable[] items) + { + foreach (var item in items.ToArray()) + availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); + } } } diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 9bcdc6e08b..7931a5ec41 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; @@ -17,6 +18,9 @@ namespace osu.Game.Skinning.Editor { public class SkinSelectionHandler : SelectionHandler { + [Resolved] + private SkinEditor skinEditor { get; set; } + public override bool HandleRotation(float angle) { // TODO: this doesn't correctly account for origin/anchor specs being different in a multi-selection. @@ -72,14 +76,8 @@ namespace osu.Game.Skinning.Editor SelectionBox.CanReverse = false; } - protected override void DeleteItems(IEnumerable items) - { - foreach (var i in items) - { - ((Drawable)i).Expire(); - SelectedItems.Remove(i); - } - } + protected override void DeleteItems(IEnumerable items) => + skinEditor.DeleteItems(items.ToArray()); protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 65b8ba7b17..15cad2c817 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -37,8 +37,15 @@ namespace osu.Game.Skinning void Reload(); /// - /// Add the provided item to this target. + /// Add a new skinnable component to this target. /// + /// The component to add. void Add(ISkinnableDrawable drawable); + + /// + /// Remove an existing skinnable component to this target. + /// + /// The component to add. + public void Remove(ISkinnableDrawable component); } } diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index a4d7f621eb..f055fb2533 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -43,10 +43,7 @@ namespace osu.Game.Skinning } } - /// - /// Add a new skinnable component to this target. - /// - /// The component to add. + /// /// Thrown when attempting to add an element to a target which is not supported by the current skin. /// Thrown if the provided instance is not a . public void Add(ISkinnableDrawable component) @@ -61,6 +58,21 @@ namespace osu.Game.Skinning components.Add(component); } + /// + /// Thrown when attempting to add an element to a target which is not supported by the current skin. + /// Thrown if the provided instance is not a . + public void Remove(ISkinnableDrawable component) + { + if (content == null) + throw new NotSupportedException("Attempting to add a new component to a target container which is not supported by the current skin."); + + if (!(component is Drawable drawable)) + throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(drawable)); + + content.Remove(drawable); + components.Remove(component); + } + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); From 9069db07437154554fb6c1b55fa323dcac2c6c45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 16:22:19 +0900 Subject: [PATCH 644/708] Fix case of `hitObjects` variables --- .../Checks/CheckUnsnappedObjectsTest.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs index 639f5b5ab4..882baba8fa 100644 --- a/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckUnsnappedObjectsTest.cs @@ -102,12 +102,12 @@ namespace osu.Game.Tests.Editing.Checks }, 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 + var hitObjects = new List { getSliderMock(startTime: 98, endTime: 398.75d).Object }; - var issues = check.Run(getContext(hitobjects)).ToList(); + var issues = check.Run(getContext(hitObjects)).ToList(); Assert.That(issues, Has.Count.EqualTo(2)); Assert.That(issues.Any(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap)); @@ -124,33 +124,33 @@ namespace osu.Game.Tests.Editing.Checks return mockSlider; } - private void assertOk(List hitobjects) + private void assertOk(List hitObjects) { - Assert.That(check.Run(getContext(hitobjects)), Is.Empty); + Assert.That(check.Run(getContext(hitObjects)), Is.Empty); } - private void assert1Ms(List hitobjects, int count = 1) + private void assert1Ms(List hitObjects, int count = 1) { - var issues = check.Run(getContext(hitobjects)).ToList(); + var issues = check.Run(getContext(hitObjects)).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateSmallUnsnap)); } - private void assert2Ms(List hitobjects, int count = 1) + private void assert2Ms(List hitObjects, int count = 1) { - var issues = check.Run(getContext(hitobjects)).ToList(); + var issues = check.Run(getContext(hitObjects)).ToList(); Assert.That(issues, Has.Count.EqualTo(count)); Assert.That(issues.All(issue => issue.Template is CheckUnsnappedObjects.IssueTemplateLargeUnsnap)); } - private BeatmapVerifierContext getContext(List hitobjects) + private BeatmapVerifierContext getContext(List hitObjects) { var beatmap = new Beatmap { ControlPointInfo = cpi, - HitObjects = hitobjects + HitObjects = hitObjects }; return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); From fcb226bd206ed17e01de4aa18e0ed2ece8416f80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 16:23:45 +0900 Subject: [PATCH 645/708] Add local variable for regular access to `HitObjects` --- .../Rulesets/Edit/Checks/CheckConcurrentObjects.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs index 51277298ab..ba5fbcf58d 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckConcurrentObjects.cs @@ -23,13 +23,15 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { - for (int i = 0; i < context.Beatmap.HitObjects.Count - 1; ++i) - { - var hitobject = context.Beatmap.HitObjects[i]; + var hitObjects = context.Beatmap.HitObjects; - for (int j = i + 1; j < context.Beatmap.HitObjects.Count; ++j) + for (int i = 0; i < hitObjects.Count - 1; ++i) + { + var hitobject = hitObjects[i]; + + for (int j = i + 1; j < hitObjects.Count; ++j) { - var nextHitobject = context.Beatmap.HitObjects[j]; + var nextHitobject = 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. From 67a99c83a35ab4e4cf5281faddce1b4b689f1f42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 16:24:52 +0900 Subject: [PATCH 646/708] Tidy bindable changed code up --- osu.Game/Screens/Edit/Verify/IssueList.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index fc9d4c7526..0b1f988447 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -46,10 +46,7 @@ namespace osu.Game.Screens.Edit.Verify rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); context = new BeatmapVerifierContext(beatmap, workingBeatmap.Value, verify.InterpretedDifficulty.Value); - verify.InterpretedDifficulty.BindValueChanged(change => - { - context.InterpretedDifficulty = change.NewValue; - }); + verify.InterpretedDifficulty.BindValueChanged(difficulty => context.InterpretedDifficulty = difficulty.NewValue); RelativeSizeAxes = Axes.Both; From f5dd18f266a11ecc941b2ebd65c6ea40adb70cab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 16:53:51 +0900 Subject: [PATCH 647/708] Use existing `LoadedBeatmapSuccessfully` bool instead Co-authored-by: Salman Ahmed --- osu.Game/Screens/Play/Player.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 15983d1d62..06dfd2fdb7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -154,8 +154,7 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - // BDL load may have aborted early. - if (DrawableRuleset == null) + if (!LoadedBeatmapSuccessfully) return; // replays should never be recorded or played back when autoplay is enabled From 0655825057cf47520211f5ac99021eb2730cf5e2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 14 May 2021 14:24:56 +0300 Subject: [PATCH 648/708] Separate changing star rating display to own test --- .../Ranking/TestSceneStarRatingDisplay.cs | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs index 2ff664a0d9..64f78b0802 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Screens.Ranking.Expanded; +using osuTK; namespace osu.Game.Tests.Visual.Ranking { @@ -15,28 +16,38 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestDisplay() { - StarRatingDisplay changingStarRating = null; - AddStep("load displays", () => Child = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, Children = new Drawable[] { - new StarRatingDisplay(new StarDifficulty(1.23, 0)), - new StarRatingDisplay(new StarDifficulty(2.34, 0)), - new StarRatingDisplay(new StarDifficulty(3.45, 0)), - new StarRatingDisplay(new StarDifficulty(4.56, 0)), - new StarRatingDisplay(new StarDifficulty(5.67, 0)), - new StarRatingDisplay(new StarDifficulty(6.78, 0)), - new StarRatingDisplay(new StarDifficulty(10.11, 0)), - changingStarRating = new StarRatingDisplay(), + new StarRatingDisplay(new StarDifficulty(1.23, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, + new StarRatingDisplay(new StarDifficulty(2.34, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, + new StarRatingDisplay(new StarDifficulty(3.45, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, + new StarRatingDisplay(new StarDifficulty(4.56, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, + new StarRatingDisplay(new StarDifficulty(5.67, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, + new StarRatingDisplay(new StarDifficulty(6.78, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, + new StarRatingDisplay(new StarDifficulty(10.11, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, } }); + } - AddRepeatStep("change bottom rating", () => + [Test] + public void TestChangingStarRatingDisplay() + { + StarRatingDisplay starRating = null; + + AddStep("load display", () => Child = starRating = new StarRatingDisplay { - changingStarRating.Current.Value = new StarDifficulty(RNG.NextDouble(0, 10), RNG.Next()); + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(3f), + }); + + AddRepeatStep("change display value", () => + { + starRating.Current.Value = new StarDifficulty(RNG.NextDouble(0.0, 11.0), RNG.Next(2000)); }, 10); } } From 34aab11ff3fc9e3f9c0370ff076bfd00e25d3cb8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 14 May 2021 14:25:46 +0300 Subject: [PATCH 649/708] Add null star rating display test case --- .../Visual/Ranking/TestSceneStarRatingDisplay.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs index 64f78b0802..7730d6868d 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs @@ -33,6 +33,17 @@ namespace osu.Game.Tests.Visual.Ranking }); } + [Test] + public void TestNullStarRatingDisplay() + { + AddStep("load null", () => Child = new StarRatingDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(3f), + }); + } + [Test] public void TestChangingStarRatingDisplay() { From e0728a6e194297b55eb8f9f4502f919ba3907a0d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 14 May 2021 15:52:36 +0300 Subject: [PATCH 650/708] Make `BeatmapDifficultyCache.GetDifficultyAsync` virtual --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 53d82c385d..6ed623d0c0 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -103,8 +103,8 @@ namespace osu.Game.Beatmaps /// The s to get the difficulty with. /// An optional which stops computing the star difficulty. /// The . - public Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable mods = null, - CancellationToken cancellationToken = default) + public virtual Task GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, + [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; From db361efecfacce01093783caa0031bee658cdd02 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 14 May 2021 15:52:51 +0300 Subject: [PATCH 651/708] Add test beatmap difficulty cache with calc. blocking support --- .../TestSceneBeatmapMetadataDisplay.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 230822e070..271fbde5c3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -4,12 +4,15 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -24,6 +27,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Resolved] private BeatmapManager manager { get; set; } + [Cached(typeof(BeatmapDifficultyCache))] + private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache(); + [Test] public void TestLocal([Values("Beatmap", "Some long title and stuff")] string title, @@ -44,6 +50,27 @@ namespace osu.Game.Tests.Visual.SongSelect })); } + [Test] + public void TestDelayedStarRating() + { + AddStep("block calculation", () => testDifficultyCache.BlockCalculation = true); + + showMetadataForBeatmap(() => CreateWorkingBeatmap(new Beatmap + { + BeatmapInfo = + { + Metadata = new BeatmapMetadata + { + Title = "Heavy beatmap", + }, + Version = "10k objects", + StarDifficulty = 99.99f, + } + })); + + AddStep("allow calculation", () => testDifficultyCache.BlockCalculation = false); + } + [Test] public void TestRandomFromDatabase() { @@ -68,8 +95,11 @@ namespace osu.Game.Tests.Visual.SongSelect OsuLogo logo = new OsuLogo { Scale = new Vector2(0.15f) }; + Remove(testDifficultyCache); + Children = new Drawable[] { + testDifficultyCache, display = new BeatmapMetadataDisplay(getBeatmap(), new Bindable>(randomMods), logo) { Anchor = Anchor.Centre, @@ -85,5 +115,37 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("finish loading", () => display.Loading = false); } + + private class TestBeatmapDifficultyCache : BeatmapDifficultyCache + { + private TaskCompletionSource calculationBlocker; + + private bool blockCalculation; + + public bool BlockCalculation + { + get => blockCalculation; + set + { + if (value == blockCalculation) + return; + + blockCalculation = value; + + if (value) + calculationBlocker = new TaskCompletionSource(); + else + calculationBlocker?.SetResult(false); + } + } + + public override async Task GetDifficultyAsync(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo = null, IEnumerable mods = null, CancellationToken cancellationToken = default) + { + if (blockCalculation) + await calculationBlocker.Task; + + return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken); + } + } } } From 0dc3bfd0c11cc45f060ecb627cef78db1beb6f5c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 14 May 2021 15:55:26 +0300 Subject: [PATCH 652/708] Apply simple transforms to star rating display when ready if not --- .../Screens/Play/BeatmapMetadataDisplay.cs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 0164fe9179..6dd9900d8c 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -53,11 +53,12 @@ namespace osu.Game.Screens.Play private IBindable starDifficulty; + private FillFlowContainer versionFlow; + private StarRatingDisplay starRatingDisplay; + [BackgroundDependencyLoader] private void load(BeatmapDifficultyCache difficultyCache) { - StarRatingDisplay starRatingDisplay; - var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); AutoSizeAxes = Axes.Both; @@ -112,7 +113,7 @@ namespace osu.Game.Screens.Play loading = new LoadingLayer(true) } }, - new FillFlowContainer + versionFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.TopCentre, @@ -178,11 +179,32 @@ namespace osu.Game.Screens.Play }; starDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo); - starDifficulty.BindValueChanged(d => starRatingDisplay.Current.Value = d.NewValue, true); Loading = true; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (starDifficulty.Value != null) + starRatingDisplay.Current.Value = starDifficulty.Value; + else + { + starRatingDisplay.Hide(); + + starDifficulty.ValueChanged += d => + { + starRatingDisplay.Current.Value = d.NewValue; + + versionFlow.AutoSizeDuration = 300; + versionFlow.AutoSizeEasing = Easing.OutQuint; + + starRatingDisplay.FadeIn(300, Easing.InQuint); + }; + } + } + private class MetadataLineLabel : OsuSpriteText { public MetadataLineLabel(string text) From 6cc678f497b1cdb1aa461ebd4749e145fdc5c80f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 14 May 2021 16:01:25 +0300 Subject: [PATCH 653/708] Remove nullability and transition support from star rating display --- .../Ranking/TestSceneStarRatingDisplay.cs | 23 +++++-------- .../Ranking/Expanded/StarRatingDisplay.cs | 33 +++++-------------- 2 files changed, 17 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs index 7730d6868d..ef077970d6 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs @@ -33,33 +33,28 @@ namespace osu.Game.Tests.Visual.Ranking }); } - [Test] - public void TestNullStarRatingDisplay() - { - AddStep("load null", () => Child = new StarRatingDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(3f), - }); - } - [Test] public void TestChangingStarRatingDisplay() { StarRatingDisplay starRating = null; - AddStep("load display", () => Child = starRating = new StarRatingDisplay + AddStep("load display", () => Child = starRating = new StarRatingDisplay(new StarDifficulty(5.55, 1)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(3f), }); - AddRepeatStep("change display value", () => + AddRepeatStep("set random value", () => { - starRating.Current.Value = new StarDifficulty(RNG.NextDouble(0.0, 11.0), RNG.Next(2000)); + starRating.Current.Value = new StarDifficulty(RNG.NextDouble(0.0, 11.0), 1); }, 10); + + AddSliderStep("set exact stars", 0.0, 11.0, 5.55, d => + { + if (starRating != null) + starRating.Current.Value = new StarDifficulty(d, 1); + }); } } } diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 422833555f..625bdaf888 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Ranking.Expanded /// /// A pill that displays the star rating of a . /// - public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue + public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue { private Box background; private OsuTextFlowContainer textFlow; @@ -30,21 +30,14 @@ namespace osu.Game.Screens.Ranking.Expanded [Resolved] private OsuColour colours { get; set; } - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); - public Bindable Current + public Bindable Current { get => current.Current; set => current.Current = value; } - /// - /// Creates a new without any set, displaying a placeholder until is changed. - /// - public StarRatingDisplay() - { - } - /// /// Creates a new using an already computed . /// @@ -112,26 +105,16 @@ namespace osu.Game.Screens.Ranking.Expanded private void updateDisplay() { - const double duration = 400; - const Easing easing = Easing.OutQuint; - - double stars = Current.Value?.Stars ?? 0.00f; - - var starRatingParts = stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); + var starRatingParts = Current.Value.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); string wholePart = starRatingParts[0]; string fractionPart = starRatingParts[1]; string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - if (Current.Value == null) - background.FadeColour(Color4.SlateGray.Opacity(0.3f)); - else - { - var rating = Current.Value.Value.DifficultyRating; + var rating = Current.Value.DifficultyRating; - background.FadeColour(rating == DifficultyRating.ExpertPlus - ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(rating), duration, easing); - } + background.Colour = rating == DifficultyRating.ExpertPlus + ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) + : (ColourInfo)colours.ForDifficultyRating(rating); textFlow.Clear(); From 7c2fc9b412e5e5835ea0b949de5814224d201de7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 14 May 2021 16:12:25 +0300 Subject: [PATCH 654/708] Update usage due to nullability removal --- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 6dd9900d8c..e26dd24de7 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -130,8 +131,9 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - starRatingDisplay = new StarRatingDisplay + starRatingDisplay = new StarRatingDisplay(default) { + Alpha = 0f, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, } @@ -188,14 +190,19 @@ namespace osu.Game.Screens.Play base.LoadComplete(); if (starDifficulty.Value != null) - starRatingDisplay.Current.Value = starDifficulty.Value; + { + starRatingDisplay.Current.Value = starDifficulty.Value.Value; + starRatingDisplay.Show(); + } else { starRatingDisplay.Hide(); starDifficulty.ValueChanged += d => { - starRatingDisplay.Current.Value = d.NewValue; + Debug.Assert(d.NewValue != null); + + starRatingDisplay.Current.Value = d.NewValue.Value; versionFlow.AutoSizeDuration = 300; versionFlow.AutoSizeEasing = Easing.OutQuint; From 67dfeeb1b77eaa009aff89aa666fdf3ad12ec365 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Fri, 14 May 2021 21:23:56 +0800 Subject: [PATCH 655/708] Cleanup code in ModHidden --- .../Mods/CatchModHidden.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 14 ++++---- osu.Game/Rulesets/Mods/ModHidden.cs | 35 ------------------- 3 files changed, 8 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index bba42dea97..ba3e8f9a8f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Catch.Mods var offset = hitObject.TimePreempt * fade_out_offset_multiplier; var duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier; - using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset, true)) + using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset)) drawable.FadeOut(duration); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 45f314af7b..b0f3f49939 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -65,13 +65,13 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawableObject) { case DrawableSliderTail _: - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) drawableObject.FadeOut(fadeOut.duration); break; case DrawableSliderRepeat sliderRepeat: - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) // only apply to circle piece – reverse arrow is not affected by hidden. sliderRepeat.CirclePiece.FadeOut(fadeOut.duration); @@ -88,22 +88,22 @@ namespace osu.Game.Rulesets.Osu.Mods else { // we don't want to see the approach circle - using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt, true)) + using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) circle.ApproachCircle.Hide(); } - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) fadeTarget.FadeOut(fadeOut.duration); break; case DrawableSlider slider: - using (slider.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (slider.BeginAbsoluteSequence(fadeOut.startTime)) slider.Body.FadeOut(fadeOut.duration, Easing.Out); break; case DrawableSliderTick sliderTick: - using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime)) sliderTick.FadeOut(fadeOut.duration); break; @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Mods // hide elements we don't care about. // todo: hide background - using (spinner.BeginAbsoluteSequence(fadeOut.startTime, true)) + using (spinner.BeginAbsoluteSequence(fadeOut.startTime)) spinner.FadeOut(fadeOut.duration); break; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index df421adbe5..03932baca9 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Sprites; @@ -18,14 +17,6 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; - /// - /// Check whether the provided hitobject should be considered the "first" hideable object. - /// Can be used to skip spinners, for instance. - /// - /// The hitobject to check. - [Obsolete("Use IsFirstAdjustableObject() instead.")] // Can be removed 20210506 - protected virtual bool IsFirstHideableObject(DrawableHitObject hitObject) => true; - public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { // Default value of ScoreProcessor's Rank in Hidden Mod should be SS+ @@ -49,36 +40,10 @@ namespace osu.Game.Rulesets.Mods protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { -#pragma warning disable 618 - ApplyFirstObjectIncreaseVisibilityState(hitObject, state); -#pragma warning restore 618 } protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { -#pragma warning disable 618 - ApplyHiddenState(hitObject, state); -#pragma warning restore 618 - } - - /// - /// Apply a special visibility state to the first object in a beatmap, if the user chooses to turn on the "increase first object visibility" setting. - /// - /// The hit object to apply the state change to. - /// The state of the hit object. - [Obsolete("Use ApplyIncreasedVisibilityState() instead.")] // Can be removed 20210506 - protected virtual void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } - - /// - /// Apply a hidden state to the provided object. - /// - /// The hit object to apply the state change to. - /// The state of the hit object. - [Obsolete("Use ApplyNormalVisibilityState() instead.")] // Can be removed 20210506 - protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) - { } } } From 393ac4fdd1d6ab2ea08b0658f9d907696f1937d1 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Fri, 14 May 2021 21:30:58 +0800 Subject: [PATCH 656/708] Destruct declaration --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index b0f3f49939..4f1e91974a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -60,20 +60,20 @@ namespace osu.Game.Rulesets.Osu.Mods OsuHitObject hitObject = drawableOsuObject.HitObject; - (double startTime, double duration) fadeOut = getFadeOutParameters(drawableOsuObject); + (double startTime, double fadeDuration) = getFadeOutParameters(drawableOsuObject); switch (drawableObject) { case DrawableSliderTail _: - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) - drawableObject.FadeOut(fadeOut.duration); + using (drawableObject.BeginAbsoluteSequence(startTime)) + drawableObject.FadeOut(fadeDuration); break; case DrawableSliderRepeat sliderRepeat: - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) + using (drawableObject.BeginAbsoluteSequence(startTime)) // only apply to circle piece – reverse arrow is not affected by hidden. - sliderRepeat.CirclePiece.FadeOut(fadeOut.duration); + sliderRepeat.CirclePiece.FadeOut(fadeDuration); break; @@ -92,19 +92,19 @@ namespace osu.Game.Rulesets.Osu.Mods circle.ApproachCircle.Hide(); } - using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime)) - fadeTarget.FadeOut(fadeOut.duration); + using (drawableObject.BeginAbsoluteSequence(startTime)) + fadeTarget.FadeOut(fadeDuration); break; case DrawableSlider slider: - using (slider.BeginAbsoluteSequence(fadeOut.startTime)) - slider.Body.FadeOut(fadeOut.duration, Easing.Out); + using (slider.BeginAbsoluteSequence(startTime)) + slider.Body.FadeOut(fadeDuration, Easing.Out); break; case DrawableSliderTick sliderTick: - using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime)) - sliderTick.FadeOut(fadeOut.duration); + using (sliderTick.BeginAbsoluteSequence(startTime)) + sliderTick.FadeOut(fadeDuration); break; @@ -112,14 +112,14 @@ namespace osu.Game.Rulesets.Osu.Mods // hide elements we don't care about. // todo: hide background - using (spinner.BeginAbsoluteSequence(fadeOut.startTime)) - spinner.FadeOut(fadeOut.duration); + using (spinner.BeginAbsoluteSequence(startTime)) + spinner.FadeOut(fadeDuration); break; } } - private (double startTime, double duration) getFadeOutParameters(DrawableOsuHitObject drawableObject) + private (double startTime, double fadeDuration) getFadeOutParameters(DrawableOsuHitObject drawableObject) { switch (drawableObject) { @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Mods return getParameters(drawableObject.HitObject); } - static (double startTime, double duration) getParameters(OsuHitObject hitObject) + static (double startTime, double fadeDuration) getParameters(OsuHitObject hitObject) { var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn; var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier; From a86a4bab9137ef6781404dc116101662c4c6d858 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Fri, 14 May 2021 21:53:59 +0800 Subject: [PATCH 657/708] Remove empty override --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 -- osu.Game/Rulesets/Mods/ModHidden.cs | 9 --------- 2 files changed, 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 4f1e91974a..c2ddeb5fcc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -43,13 +43,11 @@ namespace osu.Game.Rulesets.Osu.Mods protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { - base.ApplyIncreasedVisibilityState(hitObject, state); applyState(hitObject, true); } protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { - base.ApplyNormalVisibilityState(hitObject, state); applyState(hitObject, false); } diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 03932baca9..238612b3d2 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -37,13 +36,5 @@ namespace osu.Game.Rulesets.Mods return rank; } } - - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } - - protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } } } From a4c1b9a1a7195dca0f4b5f5f078774f306c0c916 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Fri, 14 May 2021 21:56:13 +0800 Subject: [PATCH 658/708] Rename startTime to fadeStartTime --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index c2ddeb5fcc..4168293749 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -58,18 +58,18 @@ namespace osu.Game.Rulesets.Osu.Mods OsuHitObject hitObject = drawableOsuObject.HitObject; - (double startTime, double fadeDuration) = getFadeOutParameters(drawableOsuObject); + (double fadeStartTime, double fadeDuration) = getFadeOutParameters(drawableOsuObject); switch (drawableObject) { case DrawableSliderTail _: - using (drawableObject.BeginAbsoluteSequence(startTime)) + using (drawableObject.BeginAbsoluteSequence(fadeStartTime)) drawableObject.FadeOut(fadeDuration); break; case DrawableSliderRepeat sliderRepeat: - using (drawableObject.BeginAbsoluteSequence(startTime)) + using (drawableObject.BeginAbsoluteSequence(fadeStartTime)) // only apply to circle piece – reverse arrow is not affected by hidden. sliderRepeat.CirclePiece.FadeOut(fadeDuration); @@ -90,18 +90,18 @@ namespace osu.Game.Rulesets.Osu.Mods circle.ApproachCircle.Hide(); } - using (drawableObject.BeginAbsoluteSequence(startTime)) + using (drawableObject.BeginAbsoluteSequence(fadeStartTime)) fadeTarget.FadeOut(fadeDuration); break; case DrawableSlider slider: - using (slider.BeginAbsoluteSequence(startTime)) + using (slider.BeginAbsoluteSequence(fadeStartTime)) slider.Body.FadeOut(fadeDuration, Easing.Out); break; case DrawableSliderTick sliderTick: - using (sliderTick.BeginAbsoluteSequence(startTime)) + using (sliderTick.BeginAbsoluteSequence(fadeStartTime)) sliderTick.FadeOut(fadeDuration); break; @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Mods // hide elements we don't care about. // todo: hide background - using (spinner.BeginAbsoluteSequence(startTime)) + using (spinner.BeginAbsoluteSequence(fadeStartTime)) spinner.FadeOut(fadeDuration); break; From 8b4e6d29112db730e873dd5828bcb3cd6cda514c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 21:52:09 +0200 Subject: [PATCH 659/708] Remove no longer necessary `FinishTransforms(true)` call As the component no longer has any transition transforms applied. --- osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 625bdaf888..7aba699216 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -100,7 +100,6 @@ namespace osu.Game.Screens.Ranking.Expanded base.LoadComplete(); Current.BindValueChanged(_ => updateDisplay(), true); - FinishTransforms(true); } private void updateDisplay() From 1c92b3a8f52af41f45d026c41a0d09ce890a7b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 21:58:04 +0200 Subject: [PATCH 660/708] De-duplicate star rating display creation in test --- .../Ranking/TestSceneStarRatingDisplay.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs index ef077970d6..566452249f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,16 +21,20 @@ namespace osu.Game.Tests.Visual.Ranking { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Children = new Drawable[] + ChildrenEnumerable = new[] { - new StarRatingDisplay(new StarDifficulty(1.23, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new StarRatingDisplay(new StarDifficulty(2.34, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new StarRatingDisplay(new StarDifficulty(3.45, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new StarRatingDisplay(new StarDifficulty(4.56, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new StarRatingDisplay(new StarDifficulty(5.67, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new StarRatingDisplay(new StarDifficulty(6.78, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new StarRatingDisplay(new StarDifficulty(10.11, 0)) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, - } + 1.23, + 2.34, + 3.45, + 4.56, + 5.67, + 6.78, + 10.11, + }.Select(starRating => new StarRatingDisplay(new StarDifficulty(starRating, 0)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }) }); } From 876f53bf3beb7c2f212b1634579326a5b2c7c6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 22:15:43 +0200 Subject: [PATCH 661/708] Fix copy-paste oversights in xmldoc & exception messages --- osu.Game/Skinning/ISkinnableTarget.cs | 4 ++-- osu.Game/Skinning/SkinnableTargetContainer.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/ISkinnableTarget.cs b/osu.Game/Skinning/ISkinnableTarget.cs index 15cad2c817..8d4f4dd0c3 100644 --- a/osu.Game/Skinning/ISkinnableTarget.cs +++ b/osu.Game/Skinning/ISkinnableTarget.cs @@ -43,9 +43,9 @@ namespace osu.Game.Skinning void Add(ISkinnableDrawable drawable); /// - /// Remove an existing skinnable component to this target. + /// Remove an existing skinnable component from this target. /// - /// The component to add. + /// The component to remove. public void Remove(ISkinnableDrawable component); } } diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index f055fb2533..c195a5516f 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Skinning public void Remove(ISkinnableDrawable component) { if (content == null) - throw new NotSupportedException("Attempting to add a new component to a target container which is not supported by the current skin."); + throw new NotSupportedException("Attempting to remove a new component from a target container which is not supported by the current skin."); if (!(component is Drawable drawable)) throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(drawable)); From 743b4fbff15bb8d506f4848b1c1ed9a803339253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 22:16:37 +0200 Subject: [PATCH 662/708] Pass correct member name to `ArgumentException`s --- osu.Game/Skinning/SkinnableTargetContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs index c195a5516f..d454e199dc 100644 --- a/osu.Game/Skinning/SkinnableTargetContainer.cs +++ b/osu.Game/Skinning/SkinnableTargetContainer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Skinning throw new NotSupportedException("Attempting to add a new component to a target container which is not supported by the current skin."); if (!(component is Drawable drawable)) - throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(drawable)); + throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(component)); content.Add(drawable); components.Add(component); @@ -67,7 +67,7 @@ namespace osu.Game.Skinning throw new NotSupportedException("Attempting to remove a new component from a target container which is not supported by the current skin."); if (!(component is Drawable drawable)) - throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(drawable)); + throw new ArgumentException($"Provided argument must be of type {nameof(Drawable)}.", nameof(component)); content.Remove(drawable); components.Remove(component); From 3d3c5028e6df652f53706e24d7b8ee0f58d09c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 22:33:26 +0200 Subject: [PATCH 663/708] Trim unnecessary array copy --- 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 c594f8d271..f24b0c71c0 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -243,7 +243,7 @@ namespace osu.Game.Skinning.Editor public void DeleteItems(ISkinnableDrawable[] items) { - foreach (var item in items.ToArray()) + foreach (var item in items) availableTargets.FirstOrDefault(t => t.Components.Contains(item))?.Remove(item); } } From 044770f1a265f6a7f1b4d34858384dee6a43c4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:29:34 +0200 Subject: [PATCH 664/708] Locally suppress warning in `SerializationReader` `SerializationReader` is not written in a form that would support turning nullability checking on for the entire class. The biggest problem there is the inner `DynamicDeserializer` static class, whose members are initialised via an `initialize()` method, which the compiler knows nothing about. For this reason, just opt to suppress the single inspection about returning a `null` from a method with a return type of `string` (rider expects `string?`). It would have been also viable to enable nullability checking for this one method, but that's pretty much the same thing and adds no safety anyways, so just disable the warning to minimise surprise. --- osu.Game/IO/Legacy/SerializationReader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 17cbd19838..00f90f78e3 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -38,6 +38,7 @@ namespace osu.Game.IO.Legacy /// Reads a string from the buffer. Overrides the base implementation so it can cope with nulls. public override string ReadString() { + // ReSharper disable once AssignNullToNotNullAttribute if (ReadByte() == 0) return null; return base.ReadString(); From aaa7c7eb0593c9b04c728aee778ff1de5a08f6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:33:17 +0200 Subject: [PATCH 665/708] Handle null case explicitly in `SpectatorState.Equals()` Uses the usual pattern of two `ReferenceEquals` checks against `this` and `null` before proceeding to inspect field values. Doing this causes the compiler to infer that at the point that field values are checked, `other` can no longer viably be `null`. --- osu.Game/Online/Spectator/SpectatorState.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 96a875bc14..ebb91e4dd2 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -24,7 +24,13 @@ namespace osu.Game.Online.Spectator [Key(2)] public IEnumerable Mods { get; set; } = Enumerable.Empty(); - public bool Equals(SpectatorState other) => BeatmapID == other?.BeatmapID && Mods.SequenceEqual(other?.Mods) && RulesetID == other?.RulesetID; + public bool Equals(SpectatorState other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID; + } public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID}"; } From c9facf70f9503bd3928b3ddad35807d21df633b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:35:06 +0200 Subject: [PATCH 666/708] Use conditional nullability attribute As it turns out, C# 8 provides an attribute that allows annotating that an `out` parameter's nullability depends on the method's return value, which is exactly what is desired here. --- osu.Game.Tests/Mods/ModUtilsTest.cs | 2 +- osu.Game/Utils/ModUtils.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 7dcaabca3d..7384471c41 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -146,7 +146,7 @@ namespace osu.Game.Tests.Mods if (isValid) Assert.IsNull(invalid); else - Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); + Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } public abstract class CustomMod1 : Mod diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 596880f2e7..1c3558fc90 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -92,7 +92,7 @@ namespace osu.Game.Utils /// The mods to check. /// Invalid mods, if any were found. Can be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. - public static bool CheckValidForGameplay(IEnumerable mods, out List? invalidMods) + public static bool CheckValidForGameplay(IEnumerable mods, [NotNullWhen(false)] out List? invalidMods) { mods = mods.ToArray(); From f716fb09686682f8b9061b2b532866848db0fdf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:38:44 +0200 Subject: [PATCH 667/708] Remove bogus `InternalChildren` null-check `InternalChildren` can't viably be `null`, and if it were, we have bigger problems. The removed null-check was triggering false-positive inspections further down. --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs index eb92097204..6fc59ea0e8 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy base.Update(); // store X before checking wide enough so if we perform layout there is no positional discrepancy. - float currentX = (InternalChildren?.FirstOrDefault()?.X ?? 0) - (float)Clock.ElapsedFrameTime * 0.1f; + float currentX = (InternalChildren.FirstOrDefault()?.X ?? 0) - (float)Clock.ElapsedFrameTime * 0.1f; // ensure we have enough sprites if (!InternalChildren.Any() From 483e0dd9431efe347f01ac38ef86e1190b8d8f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:39:36 +0200 Subject: [PATCH 668/708] Pass placeholder hitobject instead of `null` --- .../Skinning/TestSceneTaikoScroller.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs index 4ae3cbd418..14c3599fcd 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs @@ -5,6 +5,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Skinning.Legacy; using osu.Game.Skinning; @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning })); AddToggleStep("Toggle passing", passing => this.ChildrenOfType().ForEach(s => s.LastResult.Value = - new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Great : HitResult.Miss })); + new JudgementResult(new HitObject(), new Judgement()) { Type = passing ? HitResult.Great : HitResult.Miss })); AddToggleStep("toggle playback direction", reversed => this.reversed = reversed); } From f2d0f7db9928b60f84b23b14c07488751df86917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:40:17 +0200 Subject: [PATCH 669/708] Remove list null-checks in `LogoTrackingContainer` test If the null-checks were tripped, the test would crash anyway. It is not possible to call `.Any()` and get a valid result instead of an exception on a null reference. --- .../Visual/UserInterface/TestSceneLogoTrackingContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs index 5582cc6826..b46d35a84d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs @@ -264,7 +264,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void moveLogoFacade() { - if (!(logoFacade?.Transforms).Any() && !(transferContainer?.Transforms).Any()) + if (!logoFacade.Transforms.Any() && !transferContainer.Transforms.Any()) { Random random = new Random(); trackingContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); From 43c73f9583e31d55cc384ca8fa445011c449ec6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:42:56 +0200 Subject: [PATCH 670/708] Mark access to exception if task faulted as safe There are seemingly no C#-side compile-time guarantees that it is safe, but if the task's state is `Faulted` (as is checked right before), the exception cannot be null as per the documentation. --- osu.Game/Extensions/TaskExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index 76a76c0c52..17f1a491f8 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -6,6 +6,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.Extensions { @@ -50,7 +51,7 @@ namespace osu.Game.Extensions } else if (continuationTask.IsFaulted) { - tcs.TrySetException(continuationTask.Exception); + tcs.TrySetException(continuationTask.Exception.AsNonNull()); } else { From 628e7a71edc56fb2ff2f1aec5d44646b3b1a343f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:45:58 +0200 Subject: [PATCH 671/708] Ignore possible nulls in `Type.GetType()` calls They're mostly used in extensibility scenarios, so everything happens in runtime. There is no better resolution than to crash with a null reference exception. --- osu.Game/IO/Serialization/Converters/TypedListConverter.cs | 3 ++- osu.Game/Rulesets/RulesetInfo.cs | 3 ++- osu.Game/Rulesets/RulesetStore.cs | 3 ++- osu.Game/Skinning/SkinInfo.cs | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 50b28ea74b..174fbf9983 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.IO.Serialization.Converters { @@ -60,7 +61,7 @@ namespace osu.Game.IO.Serialization.Converters throw new JsonException("Expected $type token."); var typeName = lookupTable[(int)tok["$type"]]; - var instance = (T)Activator.CreateInstance(Type.GetType(typeName)); + var instance = (T)Activator.CreateInstance(Type.GetType(typeName).AsNonNull()); serializer.Populate(itemReader, instance); list.Add(instance); diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 702bf35fa8..59ec9cdd7e 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; namespace osu.Game.Rulesets @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets { if (!Available) return null; - var ruleset = (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo)); + var ruleset = (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo).AsNonNull()); // overwrite the pre-populated RulesetInfo with a potentially database attached copy. ruleset.RulesetInfo = this; diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 4261ee3d47..0a34ca9598 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Reflection; using osu.Framework; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; @@ -111,7 +112,7 @@ namespace osu.Game.Rulesets { try { - var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo))).RulesetInfo; + var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo).AsNonNull())).RulesetInfo; r.Name = instanceInfo.Name; r.ShortName = instanceInfo.ShortName; diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index bc57a8e71c..55760876e3 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.IO.Stores; using osu.Game.Configuration; using osu.Game.Database; @@ -32,7 +33,7 @@ namespace osu.Game.Skinning var type = string.IsNullOrEmpty(InstantiationInfo) // handle the case of skins imported before InstantiationInfo was added. ? typeof(LegacySkin) - : Type.GetType(InstantiationInfo); + : Type.GetType(InstantiationInfo).AsNonNull(); if (type == typeof(DefaultLegacySkin)) return (Skin)Activator.CreateInstance(type, this, legacyDefaultResources, resources); From 5b2b701915f1c387856742d93a19a620a2ffb368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:47:35 +0200 Subject: [PATCH 672/708] Ignore possible null in `GetResponseString()` A null there indicates a deserialisation error and therefore due to the catch block immediately succeeding the changed line everything will continue to work as intended. --- osu.Game/Online/API/APIAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 944525c119..1686595512 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -270,7 +270,7 @@ namespace osu.Game.Online.API { try { - return JObject.Parse(req.GetResponseString()).SelectToken("form_error", true).AsNonNull().ToObject(); + return JObject.Parse(req.GetResponseString().AsNonNull()).SelectToken("form_error", true).AsNonNull().ToObject(); } catch { From fa6b5515b7752932c0ffb9f035659f3c2ab1aedb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:49:41 +0200 Subject: [PATCH 673/708] Ignore possible null from `JsonConvert.DeserializeObject()` Nothing better can be done if a `null` is indeed returned. --- osu.Game/Skinning/Skin.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 2944c7a8ec..f43283f624 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -8,6 +8,7 @@ using System.Text; using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; @@ -57,7 +58,7 @@ namespace osu.Game.Skinning string jsonContent = Encoding.UTF8.GetString(bytes); - DrawableComponentInfo[skinnableTarget] = JsonConvert.DeserializeObject>(jsonContent).ToArray(); + DrawableComponentInfo[skinnableTarget] = JsonConvert.DeserializeObject>(jsonContent).AsNonNull().ToArray(); } } From b51d0380882c33bae1723af63036533f6ed2d8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:50:25 +0200 Subject: [PATCH 674/708] Ignore possible path-related nulls They're all in test code anyway, so any issue there will cause a test to fail. --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 3 ++- osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 3 ++- osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 0c35e9471d..0d117f8755 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -12,6 +12,7 @@ using osu.Framework.Platform; using osu.Game.IPC; using osu.Framework.Allocation; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; @@ -264,7 +265,7 @@ namespace osu.Game.Tests.Beatmaps.IO // change filename var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First()); - firstFile.MoveTo(Path.Combine(firstFile.DirectoryName, $"{firstFile.Name}-changed{firstFile.Extension}")); + firstFile.MoveTo(Path.Combine(firstFile.DirectoryName.AsNonNull(), $"{firstFile.Name}-changed{firstFile.Extension}")); using (var zip = ZipArchive.Create()) { diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 1c5e551042..a97f6defe9 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -9,6 +9,7 @@ using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Audio.Track; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; @@ -164,7 +165,7 @@ namespace osu.Game.Tests.Beatmaps private Stream openResource(string name) { - var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)); + var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)).AsNonNull(); return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}"); } diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index 748a52d1c5..e10bf08da4 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Reflection; using NUnit.Framework; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; @@ -41,7 +42,7 @@ namespace osu.Game.Tests.Beatmaps private Stream openResource(string name) { - var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)); + var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)).AsNonNull(); return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}"); } From e62e473bb262f368988dcfdbd417ca272f8a748a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:51:06 +0200 Subject: [PATCH 675/708] Ignore possible null in multiplayer test A null value will fail the test anyhow. --- .../NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 14589f8e6c..adc1d6aede 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -4,6 +4,7 @@ using System.Linq; using Humanizer; using NUnit.Framework; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Online.Multiplayer; using osu.Game.Tests.Visual.Multiplayer; @@ -34,7 +35,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer changeState(6, MultiplayerUserState.WaitingForLoad); checkPlayingUserCount(6); - AddStep("another user left", () => Client.RemoveUser(Client.Room?.Users.Last().User)); + AddStep("another user left", () => Client.RemoveUser((Client.Room?.Users.Last().User).AsNonNull())); checkPlayingUserCount(5); AddStep("leave room", () => Client.LeaveRoom()); From d581e0a2524c6c584667d5017e9ab92e81a8de0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:52:16 +0200 Subject: [PATCH 676/708] Ignore possible nulls in `NotifyCollectionChangedArgs` Safe to access by the virtue of the preceding case labels on `args.Action`. And they're in test code anyways. --- .../Visual/Online/TestSceneLeaderboardModSelector.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index 54e655d4ec..fc438ce6dd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -45,14 +46,14 @@ namespace osu.Game.Tests.Visual.Online switch (args.Action) { case NotifyCollectionChangedAction.Add: - args.NewItems.Cast().ForEach(mod => selectedMods.Add(new OsuSpriteText + args.NewItems.AsNonNull().Cast().ForEach(mod => selectedMods.Add(new OsuSpriteText { Text = mod.Acronym, })); break; case NotifyCollectionChangedAction.Remove: - args.OldItems.Cast().ForEach(mod => + args.OldItems.AsNonNull().Cast().ForEach(mod => { foreach (var selected in selectedMods) { From ae71389ebe18bcd3f3c1ca3793088c13fe041fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 May 2021 23:53:37 +0200 Subject: [PATCH 677/708] Ignore possible nulls from stream reader in IPC Any failures will be caught. They're not logged, but they also weren't before. Error handling can be improved at a future date, this series of changes is primarily intending to unblock a new inspection. --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 71417d1cc6..f538d4a7d9 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -7,6 +7,7 @@ using System.Linq; using JetBrains.Annotations; using Microsoft.Win32; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; @@ -77,8 +78,8 @@ namespace osu.Game.Tournament.IPC using (var stream = IPCStorage.GetStream(file_ipc_filename)) using (var sr = new StreamReader(stream)) { - var beatmapId = int.Parse(sr.ReadLine()); - var mods = int.Parse(sr.ReadLine()); + var beatmapId = int.Parse(sr.ReadLine().AsNonNull()); + var mods = int.Parse(sr.ReadLine().AsNonNull()); if (lastBeatmapId != beatmapId) { @@ -124,7 +125,7 @@ namespace osu.Game.Tournament.IPC using (var stream = IPCStorage.GetStream(file_ipc_state_filename)) using (var sr = new StreamReader(stream)) { - State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine()); + State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine().AsNonNull()); } } catch (Exception) From 69fc072429bc31ad23a67a1dacf165cf341d6fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 15 May 2021 01:01:27 +0200 Subject: [PATCH 678/708] Ignore skin component json data if deserialisation fails instead Crashing was not really the best thing to do there given the preceding code that already allowed a few continues in case of a missing file. --- osu.Game/Skinning/Skin.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index f43283f624..b6cb8fc7a4 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -8,7 +8,6 @@ using System.Text; using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; @@ -57,8 +56,12 @@ namespace osu.Game.Skinning continue; string jsonContent = Encoding.UTF8.GetString(bytes); + var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); - DrawableComponentInfo[skinnableTarget] = JsonConvert.DeserializeObject>(jsonContent).AsNonNull().ToArray(); + if (deserializedContent == null) + continue; + + DrawableComponentInfo[skinnableTarget] = deserializedContent.ToArray(); } } From 63ac430386a631ea683496527aa7f8cb47b5f065 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 15 May 2021 11:26:16 +0800 Subject: [PATCH 679/708] Rename startTime in parameters --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 4168293749..2752feb0a1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private (double startTime, double fadeDuration) getFadeOutParameters(DrawableOsuHitObject drawableObject) + private (double fadeStartTime, double fadeDuration) getFadeOutParameters(DrawableOsuHitObject drawableObject) { switch (drawableObject) { @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Mods return getParameters(drawableObject.HitObject); } - static (double startTime, double fadeDuration) getParameters(OsuHitObject hitObject) + static (double fadeStartTime, double fadeDuration) getParameters(OsuHitObject hitObject) { var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn; var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier; From 6e5c4ed7c6c3e4890c1480d7ccac1d52b131c8cf Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 15 May 2021 11:37:04 +0800 Subject: [PATCH 680/708] Revert "Remove empty override" This reverts commit a86a4bab9137ef6781404dc116101662c4c6d858. --- osu.Game/Rulesets/Mods/ModHidden.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 238612b3d2..03932baca9 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -36,5 +37,13 @@ namespace osu.Game.Rulesets.Mods return rank; } } + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } } } From 166974506ea25739eb3ad48c7e9206370a684b4b Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 15 May 2021 11:51:39 +0800 Subject: [PATCH 681/708] Duplicate implementions --- osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs | 6 ++++-- osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs | 9 +++++++++ osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 9 +++++++++ osu.Game/Rulesets/Mods/ModHidden.cs | 9 --------- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index ba3e8f9a8f..7bad4c79cb 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -28,10 +28,12 @@ namespace osu.Game.Rulesets.Catch.Mods catchPlayfield.CatcherArea.MovableCatcher.CatchFruitOnPlate = false; } + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { - base.ApplyNormalVisibilityState(hitObject, state); - if (!(hitObject is DrawableCatchHitObject catchDrawable)) return; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs index 87501d07a5..3c24e91d54 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Mods @@ -39,5 +40,13 @@ namespace osu.Game.Rulesets.Mania.Mods })); } } + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index a6f902208c..7739ecaf5b 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Mods { @@ -10,5 +11,13 @@ namespace osu.Game.Rulesets.Taiko.Mods public override string Description => @"Beats fade out before you hit them!"; public override double ScoreMultiplier => 1.06; public override bool HasImplementation => false; + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) + { + } } } diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 03932baca9..238612b3d2 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -37,13 +36,5 @@ namespace osu.Game.Rulesets.Mods return rank; } } - - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } - - protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) - { - } } } From 34d14907548cef21ae02cd8bf3eb7d26a3d16a3b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 15 May 2021 10:00:04 +0300 Subject: [PATCH 682/708] Remove null conditional Shouldn't guard against that here. --- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index e26dd24de7..fd1150650c 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(BeatmapDifficultyCache difficultyCache) { - var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); + var metadata = beatmap.BeatmapInfo.Metadata; AutoSizeAxes = Axes.Both; Children = new Drawable[] From a91f2d3dbad5e4a41dec719fa57ba5279b64ccd2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 16 May 2021 10:17:04 +0800 Subject: [PATCH 683/708] Change "judgment" to "judgement" --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index ea3eb5eb5c..8993a9b18a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.UI var hitWindows = new OsuHitWindows(); foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) - poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgmentLoaded)); + poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded)); AddRangeInternal(poolDictionary.Values); @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.UI } } - private void onJudgmentLoaded(DrawableOsuJudgement judgement) + private void onJudgementLoaded(DrawableOsuJudgement judgement) { judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent()); } From 3519398a224ba44f2f2e91ba6dd3aa921741a6a9 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 16 May 2021 11:16:12 +0800 Subject: [PATCH 684/708] Added "flip" mod for taiko --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs | 32 ++++++++++++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs new file mode 100644 index 0000000000..1f2f2c4784 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.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 osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModFlip : Mod, IApplicableToBeatmap + { + public override string Name => "Flip"; + public override string Acronym => "FP"; + public override string Description => @"Dons become kats, kats become dons"; + public override ModType Type => ModType.Conversion; + public override double ScoreMultiplier => 1; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var taikoBeatmap = (TaikoBeatmap)beatmap; + + foreach (var obj in taikoBeatmap.HitObjects) + { + if (obj is Hit hit) + hit.Type = hit.Type == HitType.Centre ? HitType.Rim : HitType.Centre; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index f4e158ec32..26dc1bd416 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Taiko case ModType.Conversion: return new Mod[] { - new TaikoModRandom(), + new MultiMod(new TaikoModFlip(), new TaikoModRandom()), new TaikoModDifficultyAdjust(), new TaikoModClassic(), }; From da13be6dd0d9ec1dca90b46649e9a3e6a3f6c31d Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 16 May 2021 11:28:11 +0800 Subject: [PATCH 685/708] Trimmed trailing white space --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs index 1f2f2c4784..e2de6ccc3d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Mods foreach (var obj in taikoBeatmap.HitObjects) { if (obj is Hit hit) - hit.Type = hit.Type == HitType.Centre ? HitType.Rim : HitType.Centre; + hit.Type = hit.Type == HitType.Centre ? HitType.Rim : HitType.Centre; } } } From 3d83741a23c6c0be53c71000148a3d7531e580c2 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 16 May 2021 12:03:03 +0800 Subject: [PATCH 686/708] Separate Flip and Random --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs | 3 +++ osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs | 3 +++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs index e2de6ccc3d..22e627ef06 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.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.Linq; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -17,6 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override string Description => @"Dons become kats, kats become dons"; public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index 1cf19ac18e..e203bbc431 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.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.Linq; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -12,6 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModRandom : ModRandom, IApplicableToBeatmap { public override string Description => @"Shuffle around the colours!"; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModFlip)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 26dc1bd416..52088b7ac4 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -133,7 +133,8 @@ namespace osu.Game.Rulesets.Taiko case ModType.Conversion: return new Mod[] { - new MultiMod(new TaikoModFlip(), new TaikoModRandom()), + new TaikoModFlip(), + new TaikoModRandom(), new TaikoModDifficultyAdjust(), new TaikoModClassic(), }; From cbc2a38b590429ccf07d4aea7e8d4083899aba01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 May 2021 13:21:06 +0900 Subject: [PATCH 687/708] Move new mod to end to avoid reordering --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 52088b7ac4..9232ea63ee 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -133,10 +133,10 @@ namespace osu.Game.Rulesets.Taiko case ModType.Conversion: return new Mod[] { - new TaikoModFlip(), new TaikoModRandom(), new TaikoModDifficultyAdjust(), new TaikoModClassic(), + new TaikoModFlip(), }; case ModType.Automation: From 422a3b76b64532f69b1432ff01d2c0d39aa68f12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 May 2021 13:21:19 +0900 Subject: [PATCH 688/708] Remove unused using statements --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs index 22e627ef06..bfab98fbf8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs @@ -3,9 +3,7 @@ using System; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; From 5972e43bc2a038df5e8c2c8f97e17ae28fe79399 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 16 May 2021 12:51:40 +0800 Subject: [PATCH 689/708] Renamed TaikoModFlip to TaikoModInvert --- osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 12 +----------- .../Mods/{TaikoModFlip.cs => TaikoModInvert.cs} | 6 +----- osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Mods/ModInvert.cs | 16 ++++++++++++++++ 5 files changed, 20 insertions(+), 18 deletions(-) rename osu.Game.Rulesets.Taiko/Mods/{TaikoModFlip.cs => TaikoModInvert.cs} (77%) create mode 100644 osu.Game/Rulesets/Mods/ModInvert.cs diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 1ea45c295c..0366c0f374 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Sprites; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; @@ -13,19 +12,10 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModInvert : Mod, IApplicableAfterBeatmapConversion + public class ManiaModInvert : ModInvert, IApplicableAfterBeatmapConversion { - public override string Name => "Invert"; - - public override string Acronym => "IN"; - public override double ScoreMultiplier => 1; - public override string Description => "Hold the keys. To the beat."; - public override IconUsage? Icon => FontAwesome.Solid.YinYang; - - public override ModType Type => ModType.Conversion; - public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs similarity index 77% rename from osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs rename to osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs index bfab98fbf8..a6ccebaa61 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs @@ -10,13 +10,9 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModFlip : Mod, IApplicableToBeatmap + public class TaikoModInvert : ModInvert, IApplicableToBeatmap { - public override string Name => "Flip"; - public override string Acronym => "FP"; public override string Description => @"Dons become kats, kats become dons"; - public override ModType Type => ModType.Conversion; - public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index e203bbc431..a5ab176176 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModRandom : ModRandom, IApplicableToBeatmap { public override string Description => @"Shuffle around the colours!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModFlip)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModInvert)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9232ea63ee..8a716bf56e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModRandom(), new TaikoModDifficultyAdjust(), new TaikoModClassic(), - new TaikoModFlip(), + new TaikoModInvert(), }; case ModType.Automation: diff --git a/osu.Game/Rulesets/Mods/ModInvert.cs b/osu.Game/Rulesets/Mods/ModInvert.cs new file mode 100644 index 0000000000..6c211d2173 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModInvert.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModInvert : Mod + { + public override string Name => "Invert"; + public override string Acronym => "IN"; + public override ModType Type => ModType.Conversion; + public override IconUsage? Icon => FontAwesome.Solid.YinYang; + public override double ScoreMultiplier => 1; + } +} From c4ae70a82720a04a82502d8bacf158040ac412d9 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 May 2021 10:59:56 +0800 Subject: [PATCH 690/708] Revert "Renamed TaikoModFlip to TaikoModInvert" This reverts commit 5972e43bc2a038df5e8c2c8f97e17ae28fe79399. --- osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 12 +++++++++++- .../Mods/{TaikoModInvert.cs => TaikoModFlip.cs} | 6 +++++- osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Mods/ModInvert.cs | 16 ---------------- 5 files changed, 18 insertions(+), 20 deletions(-) rename osu.Game.Rulesets.Taiko/Mods/{TaikoModInvert.cs => TaikoModFlip.cs} (77%) delete mode 100644 osu.Game/Rulesets/Mods/ModInvert.cs diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index 0366c0f374..1ea45c295c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; @@ -12,10 +13,19 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModInvert : ModInvert, IApplicableAfterBeatmapConversion + public class ManiaModInvert : Mod, IApplicableAfterBeatmapConversion { + public override string Name => "Invert"; + + public override string Acronym => "IN"; + public override double ScoreMultiplier => 1; + public override string Description => "Hold the keys. To the beat."; + public override IconUsage? Icon => FontAwesome.Solid.YinYang; + + public override ModType Type => ModType.Conversion; + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs similarity index 77% rename from osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs rename to osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs index a6ccebaa61..bfab98fbf8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModInvert.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs @@ -10,9 +10,13 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModInvert : ModInvert, IApplicableToBeatmap + public class TaikoModFlip : Mod, IApplicableToBeatmap { + public override string Name => "Flip"; + public override string Acronym => "FP"; public override string Description => @"Dons become kats, kats become dons"; + public override ModType Type => ModType.Conversion; + public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index a5ab176176..e203bbc431 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModRandom : ModRandom, IApplicableToBeatmap { public override string Description => @"Shuffle around the colours!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModInvert)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModFlip)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 8a716bf56e..9232ea63ee 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModRandom(), new TaikoModDifficultyAdjust(), new TaikoModClassic(), - new TaikoModInvert(), + new TaikoModFlip(), }; case ModType.Automation: diff --git a/osu.Game/Rulesets/Mods/ModInvert.cs b/osu.Game/Rulesets/Mods/ModInvert.cs deleted file mode 100644 index 6c211d2173..0000000000 --- a/osu.Game/Rulesets/Mods/ModInvert.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Sprites; - -namespace osu.Game.Rulesets.Mods -{ - public abstract class ModInvert : Mod - { - public override string Name => "Invert"; - public override string Acronym => "IN"; - public override ModType Type => ModType.Conversion; - public override IconUsage? Icon => FontAwesome.Solid.YinYang; - public override double ScoreMultiplier => 1; - } -} From f34637ea9c06a7e3a00b45f3fc762a025548dfe5 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 17 May 2021 11:04:01 +0800 Subject: [PATCH 691/708] Renamed TaikoModFlip to TaikoModSwap --- osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs | 2 +- .../Mods/{TaikoModFlip.cs => TaikoModSwap.cs} | 6 +++--- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game.Rulesets.Taiko/Mods/{TaikoModFlip.cs => TaikoModSwap.cs} (86%) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index e203bbc431..a22f189d5e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModRandom : ModRandom, IApplicableToBeatmap { public override string Description => @"Shuffle around the colours!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModFlip)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModSwap)).ToArray(); public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs similarity index 86% rename from osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs rename to osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs index bfab98fbf8..3cb337c41d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlip.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs @@ -10,10 +10,10 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModFlip : Mod, IApplicableToBeatmap + public class TaikoModSwap : Mod, IApplicableToBeatmap { - public override string Name => "Flip"; - public override string Acronym => "FP"; + public override string Name => "Swap"; + public override string Acronym => "SW"; public override string Description => @"Dons become kats, kats become dons"; public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9232ea63ee..5854d4770c 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModRandom(), new TaikoModDifficultyAdjust(), new TaikoModClassic(), - new TaikoModFlip(), + new TaikoModSwap(), }; case ModType.Automation: From baa4089364f46eb59c1209dd94d2f2a78a2e718a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 16:40:42 +0900 Subject: [PATCH 692/708] Expose method to adjust header text, not whole drawable --- .../Maintenance/DirectorySelectScreen.cs | 16 +++++++++++----- .../Maintenance/MigrationSelectScreen.cs | 9 ++------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index c2366ca209..e7c69e89fe 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -10,6 +10,7 @@ using osu.Game.Screens; using osuTK; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Framework.Screens; @@ -22,7 +23,10 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private DirectorySelector directorySelector; - protected abstract OsuSpriteText CreateHeader(); + /// + /// Text to display in the header to inform the user of what they are selecting. + /// + public abstract LocalisableString HeaderText { get; } /// /// Called upon selection of a directory by the user. @@ -73,11 +77,13 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { new Drawable[] { - CreateHeader().With(header => + new OsuSpriteText { - header.Origin = Anchor.Centre; - header.Anchor = Anchor.Centre; - }) + Text = HeaderText, + Font = OsuFont.Default.With(size: 40), + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } }, new Drawable[] { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 6334bffe94..1a60ab0638 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -4,11 +4,10 @@ using System; using System.IO; using osu.Framework.Allocation; +using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -25,11 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public override bool HideOverlaysOnEnter => true; - protected override OsuSpriteText CreateHeader() => new OsuSpriteText - { - Text = "Please select a new location", - Font = OsuFont.Default.With(size: 40) - }; + public override LocalisableString HeaderText => "Please select a new location"; protected override void OnSelection(DirectoryInfo directory) { From e3b8d8ee1875d357db0198894fb8c475969f090e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 17 May 2021 16:58:54 +0900 Subject: [PATCH 693/708] Add support for overlay-coloured links --- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index d27a3fbffe..e7f47833a2 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Online.Chat @@ -20,7 +21,10 @@ namespace osu.Game.Online.Chat /// /// Each word part of a chat link (split for word-wrap support). /// - public List Parts; + public readonly List Parts; + + [Resolved(CanBeNull = true)] + private OverlayColourProvider overlayColourProvider { get; set; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); @@ -34,7 +38,7 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader] private void load(OsuColour colours) { - IdleColour = colours.Blue; + IdleColour = overlayColourProvider?.Light2 ?? colours.Blue; } protected override IEnumerable EffectTargets => Parts; From 0989aa336442e0bb15f8728ed517625320297d7a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 17 May 2021 18:07:50 +0900 Subject: [PATCH 694/708] Fix accuracy heatmap points changing colour --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 88c855d768..cb769c31b8 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Statistics var point = new HitPoint(pointType, this) { - Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) + BaseColour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) }; points[r][c] = point; @@ -234,6 +234,11 @@ namespace osu.Game.Rulesets.Osu.Statistics private class HitPoint : Circle { + /// + /// The base colour which will be lightened/darkened depending on the value of this . + /// + public Color4 BaseColour; + private readonly HitPointType pointType; private readonly AccuracyHeatmap heatmap; @@ -284,7 +289,7 @@ namespace osu.Game.Rulesets.Osu.Statistics Alpha = Math.Min(amount / lighten_cutoff, 1); if (pointType == HitPointType.Hit) - Colour = ((Color4)Colour).Lighten(Math.Max(0, amount - lighten_cutoff)); + Colour = BaseColour.Lighten(Math.Max(0, amount - lighten_cutoff)); } } From 0d7a349500bfa123cf449b66b4b08f46b8a8963b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 17 May 2021 18:16:09 +0900 Subject: [PATCH 695/708] Exclude interfaces from skinnable types --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 59420bfc87..4097624400 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -57,7 +57,10 @@ namespace osu.Game.Skinning.Editor Spacing = new Vector2(20) }; - var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)).ToArray(); + var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() + .Where(t => !t.IsInterface) + .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) + .ToArray(); foreach (var type in skinnableTypes) { From 1f3ae901ce979247c717245dc204da822a9c431f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 18:22:24 +0900 Subject: [PATCH 696/708] Expose `DrawableRuleset` for consupmtion by HUD components --- 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 ac0c921ccf..7d3afe3043 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -198,6 +198,7 @@ namespace osu.Game.Screens.Play LocalUserPlaying.BindTo(osuGame.LocalUserPlaying); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); + dependencies.CacheAs(DrawableRuleset); ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor.ApplyBeatmap(playableBeatmap); From da0913ca2d6771049c6fc8118f82dad5d36e7142 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 18:26:15 +0900 Subject: [PATCH 697/708] Make `SongProgress` a skinnable component --- .../Visual/Gameplay/TestSceneAutoplay.cs | 2 +- osu.Game/Screens/Play/Player.cs | 5 --- osu.Game/Screens/Play/SongProgress.cs | 42 ++++++++++++------- osu.Game/Skinning/DefaultSkin.cs | 5 +++ osu.Game/Skinning/HUDSkinComponents.cs | 1 + osu.Game/Skinning/LegacySkin.cs | 2 + 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index e198a8504b..e47c782bca 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void seekToBreak(int breakIndex) { AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime)); - AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime); + AddUntilStep("wait for seek to complete", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= destBreak().StartTime); BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7d3afe3043..186e73ee4c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -358,11 +358,6 @@ namespace osu.Game.Screens.Play AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, IsCounting = false }, - RequestSeek = time => - { - GameplayClockContainer.Seek(time); - GameplayClockContainer.Start(); - }, Anchor = Anchor.Centre, Origin = Anchor.Centre }, diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 4a0787bfae..dc349ddcc6 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -14,10 +14,11 @@ using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Skinning; namespace osu.Game.Screens.Play { - public class SongProgress : OverlayContainer + public class SongProgress : OverlayContainer, ISkinnableDrawable { private const int info_height = 20; private const int bottom_bar_height = 5; @@ -39,9 +40,6 @@ namespace osu.Game.Screens.Play public readonly Bindable ShowGraph = new Bindable(); - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - private double lastHitTime => objects.Last().GetEndTime() + 1; - public override bool HandleNonPositionalInput => AllowSeeking.Value; public override bool HandlePositionalInput => AllowSeeking.Value; @@ -49,6 +47,9 @@ namespace osu.Game.Screens.Play private double firstHitTime => objects.First().StartTime; + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). + private double lastHitTime => objects.Last().GetEndTime() + 1; + private IEnumerable objects; public IEnumerable Objects @@ -67,10 +68,12 @@ namespace osu.Game.Screens.Play public IClock ReferenceClock; - private IClock gameplayClock; - public SongProgress() { + RelativeSizeAxes = Axes.X; + Anchor = Anchor.BottomRight; + Origin = Anchor.BottomRight; + Children = new Drawable[] { new SongProgressDisplay @@ -96,20 +99,34 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - OnSeek = time => RequestSeek?.Invoke(time), + OnSeek = seek, }, } }, }; } + private void seek(double time) + { + if (gameplayClock == null) + return; + + // TODO: implement + } + + [Resolved(canBeNull: true)] + private GameplayClock gameplayClock { get; set; } + [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, GameplayClock clock, OsuConfigManager config) + private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset) { base.LoadComplete(); - if (clock != null) - gameplayClock = clock; + if (drawableRuleset != null) + { + AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded); + Objects = drawableRuleset.Objects; + } config.BindWith(OsuSetting.ShowProgressGraph, ShowGraph); @@ -124,11 +141,6 @@ namespace osu.Game.Screens.Play ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); } - public void BindDrawableRuleset(DrawableRuleset drawableRuleset) - { - AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded); - } - protected override void PopIn() { this.FadeIn(500, Easing.OutQuint); diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 9b4e32531a..d13ddcf22b 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Extensions; using osu.Game.IO; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Graphics; @@ -86,6 +87,7 @@ namespace osu.Game.Skinning GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)), } }; @@ -109,6 +111,9 @@ namespace osu.Game.Skinning case HUDSkinComponents.HealthDisplay: return new DefaultHealthDisplay(); + + case HUDSkinComponents.SongProgress: + return new SongProgress(); } break; diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index a345e060e5..2e6c3a9937 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -9,5 +9,6 @@ namespace osu.Game.Skinning ScoreCounter, AccuracyCounter, HealthDisplay, + SongProgress, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a6f8f45c0f..6c8d6ee45a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -17,6 +17,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osuTK.Graphics; @@ -350,6 +351,7 @@ namespace osu.Game.Skinning GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)) ?? new DefaultScoreCounter(), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(), + GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)) ?? new SongProgress(), } }; From 0c433cda861a09d3a7e6ebaeb7fa4a15645cd48a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 18:26:30 +0900 Subject: [PATCH 698/708] Update `HUDOverlay` logic to add automatic layout for bottom-aligned components --- osu.Game/Screens/Play/HUDOverlay.cs | 103 ++++++++++++---------------- 1 file changed, 43 insertions(+), 60 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a10e91dae8..dcee64ff0d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,8 +35,12 @@ namespace osu.Game.Screens.Play /// public float TopScoringElementsHeight { get; private set; } + /// + /// The total height of all the bottom of screen scoring elements. + /// + public float BottomScoringElementsHeight { get; private set; } + public readonly KeyCounterDisplay KeyCounter; - public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; @@ -59,8 +63,6 @@ namespace osu.Game.Screens.Play private static bool hasShownNotificationOnce; - public Action RequestSeek; - private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; @@ -85,45 +87,22 @@ namespace osu.Game.Screens.Play visibilityContainer = new Container { RelativeSizeAxes = Axes.Both, - Child = new GridContainer + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Content = new[] + mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents) { - new Drawable[] + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents) - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - // still need to be migrated; a bit more involved. - new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows), - } - }, - } - }, - }, - new Drawable[] - { - Progress = CreateProgress(), + // still need to be migrated; a bit more involved. + new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows), } }, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize) - } - }, + } }, topRightElements = new FillFlowContainer { @@ -164,10 +143,6 @@ namespace osu.Game.Screens.Play if (drawableRuleset != null) { BindDrawableRuleset(drawableRuleset); - - Progress.Objects = drawableRuleset.Objects; - Progress.RequestSeek = time => RequestSeek(time); - Progress.ReferenceClock = drawableRuleset.FrameStableClock; } ModDisplay.Current.Value = mods; @@ -206,26 +181,43 @@ namespace osu.Game.Screens.Play { base.Update(); - Vector2 lowestScreenSpace = Vector2.Zero; + Vector2? lowestTopScreenSpace = null; + Vector2? highestBottomScreenSpace = null; // LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. foreach (var element in mainComponents.Components.Cast()) { // for now align top-right components with the bottom-edge of the lowest top-anchored hud element. - if (!element.Anchor.HasFlagFast(Anchor.TopRight) && !element.RelativeSizeAxes.HasFlagFast(Axes.X)) + if (!element.RelativeSizeAxes.HasFlagFast(Axes.X)) continue; - // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. - if (element is LegacyHealthDisplay) - continue; + if (element.Anchor.HasFlagFast(Anchor.TopRight)) + { + // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. + if (element is LegacyHealthDisplay) + continue; - var bottomRight = element.ScreenSpaceDrawQuad.BottomRight; - if (bottomRight.Y > lowestScreenSpace.Y) - lowestScreenSpace = bottomRight; + var bottomRight = element.ScreenSpaceDrawQuad.BottomRight; + if (lowestTopScreenSpace == null || bottomRight.Y > lowestTopScreenSpace.Value.Y) + lowestTopScreenSpace = bottomRight; + } + else if (element.Anchor.HasFlagFast(Anchor.y2)) + { + var topLeft = element.ScreenSpaceDrawQuad.TopLeft; + if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y) + highestBottomScreenSpace = topLeft; + } } - topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestScreenSpace).Y; - bottomRightElements.Y = -Progress.Height; + if (lowestTopScreenSpace.HasValue) + topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestTopScreenSpace.Value).Y; + else + topRightElements.Y = 0; + + if (highestBottomScreenSpace.HasValue) + bottomRightElements.Y = BottomScoringElementsHeight = -(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y); + else + bottomRightElements.Y = 0; } private void updateVisibility() @@ -281,8 +273,6 @@ namespace osu.Game.Screens.Play (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); - - Progress.BindDrawableRuleset(drawableRuleset); } protected FailingLayer CreateFailingLayer() => new FailingLayer @@ -296,13 +286,6 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, }; - protected SongProgress CreateProgress() => new SongProgress - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - }; - protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton { Anchor = Anchor.BottomRight, From b80768b44afc84f0d4487afdbe308c62f5ccc3a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 18:41:56 +0900 Subject: [PATCH 699/708] Hook up seeking flow --- osu.Game/Screens/Play/Player.cs | 6 ++++++ osu.Game/Screens/Play/SongProgress.cs | 25 +++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 186e73ee4c..d5807f6e18 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -562,6 +562,12 @@ namespace osu.Game.Screens.Play updateSampleDisabledState(); } + /// + /// Seek to a specific time in gameplay. + /// + /// The destination time to seek to. + public void Seek(double time) => GameplayClockContainer.Seek(time); + /// /// Restart gameplay via a parent . /// This can be called from a child screen in order to trigger the restart process. diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index dc349ddcc6..f95e6520cc 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -66,7 +66,13 @@ namespace osu.Game.Screens.Play } } - public IClock ReferenceClock; + [Resolved(canBeNull: true)] + private Player player { get; set; } + + [Resolved(canBeNull: true)] + private GameplayClock gameplayClock { get; set; } + + private IClock referenceClock; public SongProgress() { @@ -99,24 +105,13 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - OnSeek = seek, + OnSeek = time => player?.Seek(time), }, } }, }; } - private void seek(double time) - { - if (gameplayClock == null) - return; - - // TODO: implement - } - - [Resolved(canBeNull: true)] - private GameplayClock gameplayClock { get; set; } - [BackgroundDependencyLoader(true)] private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset) { @@ -125,6 +120,8 @@ namespace osu.Game.Screens.Play if (drawableRuleset != null) { AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded); + + referenceClock = drawableRuleset.FrameStableClock; Objects = drawableRuleset.Objects; } @@ -159,7 +156,7 @@ namespace osu.Game.Screens.Play return; double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; - double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime; + double frameStableTime = referenceClock?.CurrentTime ?? gameplayTime; double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime)); From ecf70c1707b300d56c16e7cc171fa2b527284ff0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 18:55:18 +0900 Subject: [PATCH 700/708] Remove unnecessary container --- osu.Game/Screens/Play/SongProgress.cs | 58 +++++++++------------------ 1 file changed, 19 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index f95e6520cc..b7939b5e75 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -82,32 +82,26 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { - new SongProgressDisplay + info = new SongProgressInfo { - 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 => player?.Seek(time), - }, - } + 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 => player?.Seek(time), }, }; } @@ -188,19 +182,5 @@ 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 - { - public SongProgressDisplay() - { - // TODO: move actual implementation into this. - // exists for skin customisation purposes (interface should be added to this container). - - Masking = true; - RelativeSizeAxes = Axes.Both; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; - } - } } } From 60f3e628bcdfbf76602444f83ffa9577ef756f46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 19:05:22 +0900 Subject: [PATCH 701/708] Fix song progress being interactable inside toolbox button --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 59420bfc87..acadaa3b74 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -92,6 +92,9 @@ namespace osu.Game.Skinning.Editor private class ToolboxComponentButton : OsuButton { + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => false; + public override bool PropagateNonPositionalInputSubTree => false; + private readonly Drawable component; public Action RequestPlacement; From 42d2711dc6ca0e6cac28bd70c95d4d75272be9b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 May 2021 19:29:59 +0900 Subject: [PATCH 702/708] Use `ShouldBeConsideredForInput` instead of `ReceivePositionalInputAtSubTree` --- osu.Game/Skinning/Editor/SkinComponentToolbox.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index acadaa3b74..471ed9c6ec 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -92,7 +92,8 @@ namespace osu.Game.Skinning.Editor private class ToolboxComponentButton : OsuButton { - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => false; + protected override bool ShouldBeConsideredForInput(Drawable child) => false; + public override bool PropagateNonPositionalInputSubTree => false; private readonly Drawable component; From 9d423245d8b797f6adad68afd718bda103202d19 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 18 May 2021 13:02:23 +0900 Subject: [PATCH 703/708] Fix up xmldocs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index f59ad8bfa2..c7df2d1a76 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Edit protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; /// - /// Selects this , causing it to become visible. + /// Selects this , causing it to become visible. /// public void Select() => State = SelectionState.Selected; diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index e32dcc81ee..8a052299ce 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// An event which occurs when a is moved. + /// An event which occurs when a is moved. /// public class MoveSelectionEvent { From f1f3606fd0ce9e4fea87b72faa1b2697b226ab45 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 13:11:58 +0900 Subject: [PATCH 704/708] Fix unresolved xmldocs --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index c7df2d1a76..12ab89f79e 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Edit protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; /// - /// Selects this , causing it to become visible. + /// Selects this , causing it to become visible. /// public void Select() => State = SelectionState.Selected; diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index 8a052299ce..2b71bb2f16 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// An event which occurs when a is moved. + /// An event which occurs when a is moved. /// public class MoveSelectionEvent { From e621cfc4ea6f3656ac47cca8ad61204e09cab8c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 14:14:10 +0900 Subject: [PATCH 705/708] Add Apply() method for applying new DHOs --- .../Blueprints/HoldNoteSelectionBlueprint.cs | 21 ++++++++++++------- .../Sliders/SliderSelectionBlueprint.cs | 9 ++++++++ .../Edit/HitObjectSelectionBlueprint.cs | 20 +++++++++++++++++- .../Components/ComposeBlueprintContainer.cs | 2 +- .../Visual/SelectionBlueprintTestScene.cs | 2 +- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index c7ee6097c6..ac821e504c 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -21,6 +22,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private readonly IBindable direction = new Bindable(); + private HoldNoteNoteSelectionBlueprint headBlueprint; + private HoldNoteNoteSelectionBlueprint tailBlueprint; + [Resolved] private OsuColour colours { get; set; } @@ -33,16 +37,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private void load(IScrollingInfo scrollingInfo) { direction.BindTo(scrollingInfo.Direction); - } - - protected override void LoadComplete() - { - base.LoadComplete(); InternalChildren = new Drawable[] { - new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.Start), - new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.End), + headBlueprint = new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.Start), + tailBlueprint = new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.End), new Container { RelativeSizeAxes = Axes.Both, @@ -59,6 +58,14 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints }; } + public override void Apply(DrawableHitObject drawableObject) + { + base.Apply(drawableObject); + + headBlueprint?.Apply(drawableObject); + tailBlueprint?.Apply(drawableObject); + } + protected override void Update() { base.Update(); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 939f4cdc3e..21945853a8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -14,6 +14,7 @@ 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 osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; @@ -77,6 +78,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece.UpdateFrom(HitObject); } + public override void Apply(DrawableHitObject drawableObject) + { + base.Apply(drawableObject); + + HeadBlueprint?.Apply(drawableObject); + TailBlueprint?.Apply(drawableObject); + } + public override bool HandleQuickDeletion() { var hoveredControlPoint = ControlPointVisualiser?.Pieces.FirstOrDefault(p => p.IsHovered); diff --git a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs index 56434b1d82..6e9172c822 100644 --- a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Edit /// /// The which this applies to. /// - public DrawableHitObject DrawableObject { get; internal set; } + public virtual DrawableHitObject DrawableObject { get; private set; } /// /// Whether the blueprint should be shown even when the is not alive. @@ -28,6 +28,24 @@ namespace osu.Game.Rulesets.Edit { } + protected override void LoadAsyncComplete() + { + // Must be done before base.LoadAsyncComplete() as this may affect children. + Apply(DrawableObject); + + base.LoadAsyncComplete(); + } + + /// + /// Applies a to this . + /// The represented model does not change. + /// + /// The new . + public virtual void Apply(DrawableHitObject drawableObject) + { + DrawableObject = drawableObject; + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 95f4069edb..b3773b9ec1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (drawable == null) return null; - return CreateHitObjectBlueprintFor(item).With(b => b.DrawableObject = drawable); + return CreateHitObjectBlueprintFor(item).With(b => b.Apply(drawable)); } public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index dc12a4999d..b518465c40 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual { Add(blueprint.With(d => { - d.DrawableObject = drawableObject; + d.Apply(drawableObject); d.Depth = float.MinValue; d.Select(); })); From 532c41c82eb8f0640f6ac58f24f00623784c9371 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 14:19:11 +0900 Subject: [PATCH 706/708] Remove nested blueprints from sliders --- .../TestSceneSliderControlPointPiece.cs | 10 +++++----- .../TestSceneSliderSelectionBlueprint.cs | 14 +++++++------- ...ionBlueprint.cs => SliderCircleOverlay.cs} | 17 +++++++---------- .../Sliders/SliderSelectionBlueprint.cs | 19 +++++-------------- 4 files changed, 24 insertions(+), 36 deletions(-) rename osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/{SliderCircleSelectionBlueprint.cs => SliderCircleOverlay.cs} (53%) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index fe0f2f8a87..24b947c854 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -150,8 +150,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; - public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint; - public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; + public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; + public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) @@ -159,14 +159,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { } - protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); + protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position); } - private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint + private class TestSliderCircleOverlay : SliderCircleOverlay { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestSliderCircleBlueprint(Slider slider, SliderPosition position) + public TestSliderCircleOverlay(Slider slider, SliderPosition position) : base(slider, position) { } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index 721bb1985d..0d828a79c8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -174,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition); AddAssert("head positioned correctly", - () => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre)); + () => Precision.AlmostEquals(blueprint.HeadOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre)); AddAssert("tail positioned correctly", - () => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); + () => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); } private void moveMouseToControlPoint(int index) @@ -195,8 +195,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; - public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint; - public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; + public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; + public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) @@ -204,14 +204,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { } - protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); + protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position); } - private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint + private class TestSliderCircleOverlay : SliderCircleOverlay { public new HitCirclePiece CirclePiece => base.CirclePiece; - public TestSliderCircleBlueprint(Slider slider, SliderPosition position) + public TestSliderCircleOverlay(Slider slider, SliderPosition position) : base(slider, position) { } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs similarity index 53% rename from osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs index ff01c7a442..241ff70a18 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs @@ -1,35 +1,32 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { - public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint + public class SliderCircleOverlay : CompositeDrawable { protected readonly HitCirclePiece CirclePiece; + private readonly Slider slider; private readonly SliderPosition position; - public SliderCircleSelectionBlueprint(Slider slider, SliderPosition position) - : base(slider) + public SliderCircleOverlay(Slider slider, SliderPosition position) { + this.slider = slider; this.position = position; InternalChild = CirclePiece = new HitCirclePiece(); - - Select(); } protected override void Update() { base.Update(); - CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)HitObject.HeadCircle : HitObject.TailCircle); + CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle); } - - // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. - public override bool HandlePositionalInput => false; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 21945853a8..ec97f1fd78 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -14,7 +14,6 @@ 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 osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; @@ -27,8 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public class SliderSelectionBlueprint : OsuSelectionBlueprint { protected SliderBodyPiece BodyPiece { get; private set; } - protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; } - protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; } + protected SliderCircleOverlay HeadOverlay { get; private set; } + protected SliderCircleOverlay TailOverlay { get; private set; } [CanBeNull] protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } @@ -61,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders InternalChildren = new Drawable[] { BodyPiece = new SliderBodyPiece(), - HeadBlueprint = CreateCircleSelectionBlueprint(HitObject, SliderPosition.Start), - TailBlueprint = CreateCircleSelectionBlueprint(HitObject, SliderPosition.End), + HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start), + TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End), }; } @@ -78,14 +77,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece.UpdateFrom(HitObject); } - public override void Apply(DrawableHitObject drawableObject) - { - base.Apply(drawableObject); - - HeadBlueprint?.Apply(drawableObject); - TailBlueprint?.Apply(drawableObject); - } - public override bool HandleQuickDeletion() { var hoveredControlPoint = ControlPointVisualiser?.Pieces.FirstOrDefault(p => p.IsHovered); @@ -250,6 +241,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; - protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(Slider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); + protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position); } } From 72beddaadc2799ab2a0c72f2b9a7aae81b195a82 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 14:25:07 +0900 Subject: [PATCH 707/708] Remove nested blueprints from hold notes --- .../Editor/TestSceneManiaHitObjectComposer.cs | 4 ++-- ...ionBlueprint.cs => HoldNoteNoteOverlay.cs} | 23 ++++++++----------- .../Blueprints/HoldNoteSelectionBlueprint.cs | 16 ++----------- 3 files changed, 14 insertions(+), 29 deletions(-) rename osu.Game.Rulesets.Mania/Edit/Blueprints/{HoldNoteNoteSelectionBlueprint.cs => HoldNoteNoteOverlay.cs} (60%) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index aaf96c63a6..8474279b01 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -184,8 +184,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft)); AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft)); - AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition); - AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); + AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition); + AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); } private void setScrollStep(ScrollingDirection direction) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteOverlay.cs similarity index 60% rename from osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs rename to osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteOverlay.cs index 14c8d488a9..6933571be8 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteOverlay.cs @@ -2,35 +2,35 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint + public class HoldNoteNoteOverlay : CompositeDrawable { - protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; - + private readonly HoldNoteSelectionBlueprint holdNoteBlueprint; private readonly HoldNotePosition position; - public HoldNoteNoteSelectionBlueprint(HoldNote holdNote, HoldNotePosition position) - : base(holdNote) + public HoldNoteNoteOverlay(HoldNoteSelectionBlueprint holdNoteBlueprint, HoldNotePosition position) { + this.holdNoteBlueprint = holdNoteBlueprint; this.position = position; - InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X }; - Select(); + InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X }; } protected override void Update() { base.Update(); + var drawableObject = holdNoteBlueprint.DrawableObject; + // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. - if (DrawableObject.IsLoaded) + if (drawableObject.IsLoaded) { - DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)DrawableObject.Head : DrawableObject.Tail; + DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)drawableObject.Head : drawableObject.Tail; Anchor = note.Anchor; Origin = note.Origin; @@ -39,8 +39,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints Position = note.DrawPosition; } } - - // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input. - public override bool HandlePositionalInput => false; } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index ac821e504c..d04c5cd4aa 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -22,9 +21,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private readonly IBindable direction = new Bindable(); - private HoldNoteNoteSelectionBlueprint headBlueprint; - private HoldNoteNoteSelectionBlueprint tailBlueprint; - [Resolved] private OsuColour colours { get; set; } @@ -40,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints InternalChildren = new Drawable[] { - headBlueprint = new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.Start), - tailBlueprint = new HoldNoteNoteSelectionBlueprint(HitObject, HoldNotePosition.End), + new HoldNoteNoteOverlay(this, HoldNotePosition.Start), + new HoldNoteNoteOverlay(this, HoldNotePosition.End), new Container { RelativeSizeAxes = Axes.Both, @@ -58,14 +54,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints }; } - public override void Apply(DrawableHitObject drawableObject) - { - base.Apply(drawableObject); - - headBlueprint?.Apply(drawableObject); - tailBlueprint?.Apply(drawableObject); - } - protected override void Update() { base.Update(); From 882d54a8f8692cdbd508614b7033cff898bd070d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 May 2021 14:26:26 +0900 Subject: [PATCH 708/708] Remove now unnecessary Apply() method --- .../Edit/HitObjectSelectionBlueprint.cs | 20 +------------------ .../Components/ComposeBlueprintContainer.cs | 2 +- .../Visual/SelectionBlueprintTestScene.cs | 2 +- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs index 6e9172c822..56434b1d82 100644 --- a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Edit /// /// The which this applies to. /// - public virtual DrawableHitObject DrawableObject { get; private set; } + public DrawableHitObject DrawableObject { get; internal set; } /// /// Whether the blueprint should be shown even when the is not alive. @@ -28,24 +28,6 @@ namespace osu.Game.Rulesets.Edit { } - protected override void LoadAsyncComplete() - { - // Must be done before base.LoadAsyncComplete() as this may affect children. - Apply(DrawableObject); - - base.LoadAsyncComplete(); - } - - /// - /// Applies a to this . - /// The represented model does not change. - /// - /// The new . - public virtual void Apply(DrawableHitObject drawableObject) - { - DrawableObject = drawableObject; - } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index b3773b9ec1..95f4069edb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (drawable == null) return null; - return CreateHitObjectBlueprintFor(item).With(b => b.Apply(drawable)); + return CreateHitObjectBlueprintFor(item).With(b => b.DrawableObject = drawable); } public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null; diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index b518465c40..dc12a4999d 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual { Add(blueprint.With(d => { - d.Apply(drawableObject); + d.DrawableObject = drawableObject; d.Depth = float.MinValue; d.Select(); }));