From 6fe1b68510397ccccaf4339d2974784864ad02bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 22:30:40 +0900 Subject: [PATCH 001/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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/357] 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 627dd960b07198135b941c1a82a70338058fd21d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Apr 2021 20:52:20 +0900 Subject: [PATCH 017/357] 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 018/357] 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 019/357] 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 020/357] 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 021/357] 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 022/357] 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 023/357] 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 024/357] 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 025/357] 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 026/357] 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 027/357] 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 028/357] 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 029/357] 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 030/357] 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 031/357] 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 032/357] 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 5ac0eb02cd0bffd1c7876178c5b8ad50bad3b165 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 12:25:29 +0900 Subject: [PATCH 033/357] 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 034/357] 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 f98ffbb1b365294b424ee28938072ba59b9cf1e2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:15:42 +0900 Subject: [PATCH 035/357] 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 036/357] 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 037/357] 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 038/357] 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 039/357] 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 040/357] 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 f32d00c0d99ecccdbf1452c210f0a75769932298 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 17:13:01 +0900 Subject: [PATCH 041/357] 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 2bea6256137a82375ac2e0f05a39f95b4f286503 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 23:21:03 +0900 Subject: [PATCH 042/357] 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 043/357] 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 044/357] 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 045/357] 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 046/357] 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 047/357] 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 048/357] 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 049/357] 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 050/357] 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 051/357] 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 052/357] 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 053/357] 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 575ec7c528af97d00e7aa3e3fce940ae5e64350f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Apr 2021 19:09:54 +0900 Subject: [PATCH 054/357] 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 055/357] 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 056/357] 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 057/357] 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 058/357] 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 059/357] 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 060/357] 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 62bcc5f76d891f45addc79500350be050f3b7fe9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Apr 2021 07:49:31 +0300 Subject: [PATCH 061/357] 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 062/357] 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 063/357] 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 064/357] 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 065/357] 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 79e2b232d8a18775731bd3cca9c479d787ca64b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 25 Apr 2021 19:26:53 +0300 Subject: [PATCH 066/357] 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 067/357] 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 068/357] 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 069/357] 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 070/357] 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 071/357] 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 072/357] 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 073/357] 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 aa99c192d025240f6571eb87f856d3235e07e91c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 16:21:12 +0900 Subject: [PATCH 074/357] 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 075/357] 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 076/357] 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 077/357] 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 078/357] 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 079/357] 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 080/357] 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 081/357] 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 082/357] 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 7e11d520d587a905f64bf25d023c5515c046b812 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 21:25:34 +0900 Subject: [PATCH 083/357] 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 084/357] 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 085/357] 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 b75b9a97edfcf27dd2d47dfa95cb34a1a159ae04 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 27 Apr 2021 14:09:58 +0700 Subject: [PATCH 086/357] 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 087/357] 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 088/357] 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 089/357] 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 090/357] 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 091/357] 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 6a921af08555a0b868cacc04a623f07b69e4211d Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 28 Apr 2021 09:23:05 +0700 Subject: [PATCH 092/357] 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 736eace00ac68dd56dd86103e7a62a0eb17d13ee Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Wed, 28 Apr 2021 10:11:29 +0700 Subject: [PATCH 093/357] 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 094/357] 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 095/357] 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 096/357] 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 171e954e89b7e378f599e30d8525850b4aece2ff Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 29 Apr 2021 13:48:00 +0700 Subject: [PATCH 097/357] 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 820408757a736d3ff3c9dc59cd933bcb1874990a Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 30 Apr 2021 09:43:05 +0700 Subject: [PATCH 098/357] 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 099/357] 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 100/357] 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 101/357] 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 102/357] 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 103/357] 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 104/357] 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 105/357] 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 106/357] 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 107/357] 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 108/357] 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 109/357] 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 4770a64709e529479c69ad7a4ae024a9801f2fe0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Apr 2021 18:47:22 +0900 Subject: [PATCH 110/357] 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 111/357] 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 112/357] 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 113/357] 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 114/357] 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 115/357] 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 116/357] 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 117/357] 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 118/357] 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 119/357] 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 120/357] 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 e663629bc64ceedb95fda5adbf26c2a6c1be99cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Apr 2021 15:22:51 +0900 Subject: [PATCH 121/357] 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 88aaa9b3320124203abbbef8169b82133956e5dd Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Sun, 2 May 2021 22:35:30 +0700 Subject: [PATCH 122/357] 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 123/357] 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 8a2926c0b54e41f72cc1ba9c7110655dac392da6 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Mon, 3 May 2021 09:18:45 +0700 Subject: [PATCH 124/357] 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 125/357] 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 126/357] 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 127/357] 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 128/357] 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 129/357] 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 130/357] 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 131/357] 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 2aa21e2affb3f9993ec42b87228236e768aa7e05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 May 2021 14:37:11 +0900 Subject: [PATCH 132/357] 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 133/357] 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 134/357] 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 840c22a3b17bd01dea5b79c4a6bdd0e124b04241 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 May 2021 12:16:40 +0300 Subject: [PATCH 135/357] 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 356b1e9a2dd3492039fd7f0bd1e1112856ca2e55 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Tue, 4 May 2021 05:43:59 +0700 Subject: [PATCH 136/357] 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 137/357] 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 138/357] 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 139/357] 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 140/357] 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 141/357] 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 142/357] 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 12c1ded7a8f3c6c6e76cee5125d15a57530e70f7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 00:28:49 +0300 Subject: [PATCH 143/357] 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 2a67361dc01963e10e850ac5523858578c613f67 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 5 May 2021 21:50:11 +0300 Subject: [PATCH 144/357] 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 145/357] 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 146/357] 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 147/357] 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 148/357] 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 149/357] 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 150/357] 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 151/357] 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 152/357] 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 b6560a616a22f07e248f2063cd0d78f1fbbac180 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 15:00:12 +0700 Subject: [PATCH 153/357] 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 154/357] 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 155/357] 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 156/357] 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 010f6258705e86c47ad208c519362daa80e6c346 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Thu, 6 May 2021 17:05:41 +0700 Subject: [PATCH 157/357] 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 158/357] 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 159/357] 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 160/357] 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 161/357] 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 162/357] 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 163/357] 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 164/357] 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 bdfe44ddca7235cd7a42bac8015f2e55b13bf905 Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 7 May 2021 13:19:30 +0700 Subject: [PATCH 165/357] 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 166/357] 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 167/357] 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 168/357] 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 ad398165a2d38870173df9b82034691d8c9c8d4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 16:26:54 +0900 Subject: [PATCH 169/357] 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 170/357] 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 171/357] 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 172/357] 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 173/357] 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 174/357] 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 175/357] 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 755588258e90883f22a89cee219d6e44373b5887 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 May 2021 16:56:24 +0900 Subject: [PATCH 176/357] 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 177/357] 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 178/357] 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 179/357] 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 180/357] 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 181/357] 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 182/357] 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 183/357] 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 184/357] 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 8c564a69ed1978e2e11be2d93806c57a759c5800 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Fri, 7 May 2021 20:59:20 -0400 Subject: [PATCH 185/357] 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 25312b3e88d47b2cbc9ce327d2c6d05954b39d72 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sat, 8 May 2021 11:45:31 -0400 Subject: [PATCH 186/357] 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 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 187/357] 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 188/357] 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 342c5a5938253c961bfc97d2e1239f74ba20f4d9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 May 2021 04:49:40 +0300 Subject: [PATCH 189/357] 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 190/357] 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 191/357] 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 192/357] 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 193/357] 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 194/357] 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 195/357] 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 196/357] 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 197/357] 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 198/357] 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 199/357] 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 200/357] 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 201/357] 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 202/357] 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 35a7226cd82120207014a652567a5e87febc25e2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 10 May 2021 13:41:04 +0900 Subject: [PATCH 203/357] 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 204/357] 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 97e72849af46241de4acbe4e955c5e6a72db9434 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 10 May 2021 15:19:27 +0900 Subject: [PATCH 205/357] 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 fa872858b592278b7672c16fbd840825d8ac7a24 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 10 May 2021 16:40:06 +0900 Subject: [PATCH 206/357] 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 207/357] 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 208/357] 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 209/357] 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 210/357] 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 211/357] 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 212/357] 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 213/357] 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 214/357] 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 215/357] 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 216/357] 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 217/357] 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 218/357] 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 219/357] 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 220/357] 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 221/357] 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 222/357] 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 223/357] 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 224/357] 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 225/357] 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 226/357] 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 227/357] 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 228/357] 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 229/357] 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 230/357] 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 231/357] 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 232/357] 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 233/357] 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 234/357] 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 235/357] 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 236/357] 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 237/357] 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 238/357] 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 239/357] 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 240/357] 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 241/357] 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 242/357] 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 243/357] 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 244/357] 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 245/357] 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 246/357] 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 247/357] 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 248/357] 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 249/357] 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 250/357] 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 251/357] 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 252/357] 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 253/357] 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 254/357] 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 255/357] 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 256/357] 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 257/357] 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 258/357] 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 259/357] 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 260/357] 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 261/357] 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 262/357] 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 263/357] 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 264/357] 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 265/357] 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 266/357] 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 267/357] 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 268/357] 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 269/357] 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 270/357] 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 9b09361cc9a5b3ef1fb9cecf34e28f6401ae9def Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 12 May 2021 12:16:41 +0900 Subject: [PATCH 271/357] 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 272/357] 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 273/357] 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 274/357] 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 275/357] 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 276/357] 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 277/357] 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 278/357] 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 279/357] 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 280/357] 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 281/357] 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 282/357] 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 283/357] 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 284/357] 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 285/357] 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 286/357] 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 287/357] 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 288/357] 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 289/357] 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 290/357] 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 291/357] 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 292/357] 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 293/357] 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 294/357] 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 295/357] 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 296/357] 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 297/357] 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 298/357] 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 299/357] 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 300/357] 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 301/357] 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 302/357] 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 303/357] 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 304/357] 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 305/357] 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 306/357] 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 307/357] 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 308/357] 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 309/357] 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 310/357] 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 311/357] 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 312/357] 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 313/357] 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 314/357] 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 315/357] 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 316/357] 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 317/357] 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 318/357] 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 319/357] 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 320/357] 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 321/357] 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 322/357] 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 323/357] 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 324/357] 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 325/357] 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 326/357] 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 327/357] 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 328/357] 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 329/357] 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 330/357] 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 331/357] 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 5818ed4c8c66bb749a83e700c16fde831ba56b4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 16:41:36 +0900 Subject: [PATCH 332/357] 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 333/357] 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 334/357] 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 335/357] 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 336/357] 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 337/357] 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 338/357] 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 339/357] 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 340/357] 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 341/357] 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 342/357] 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 343/357] 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 df77b28b48b8a58a92380c458251efcaf73773c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 May 2021 18:39:21 +0900 Subject: [PATCH 344/357] 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 345/357] 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 346/357] 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 347/357] 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 348/357] 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 1e23c535070102c326ca609f56b0d68765bcc25f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 May 2021 21:59:38 +0900 Subject: [PATCH 349/357] 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 350/357] 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 351/357] 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 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 352/357] 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 ddceafb1b041f0360446e251f9a4755f42f114d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 09:38:25 +0900 Subject: [PATCH 353/357] 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 354/357] 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 46e7d9e0edbc18a48e93db72e616a7af67482c75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 12:15:56 +0900 Subject: [PATCH 355/357] 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 48672f8afd156556f29628b3ae246013f72c297d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 14 May 2021 15:02:36 +0900 Subject: [PATCH 356/357] 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 8338f702c3b260dbd01e8dac1b4c19ce01364fb6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 14 May 2021 09:32:56 +0300 Subject: [PATCH 357/357] 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()); } ///