From a3c78674a12022506f8cd42d2407e7dfe8ad176a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 18:09:24 +0900 Subject: [PATCH 01/19] Add new interface for autoplay mods --- .../Mods/EmptyFreeformModAutoplay.cs | 3 +-- .../Mods/PippidonModAutoplay.cs | 3 +-- .../Mods/EmptyScrollingModAutoplay.cs | 3 +-- .../Mods/PippidonModAutoplay.cs | 3 +-- osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs | 3 +-- osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs | 3 +-- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 3 +-- osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs | 3 +-- osu.Game/Rulesets/Mods/ICreateReplay.cs | 14 ++++++++++++++ osu.Game/Rulesets/Mods/ModAutoplay.cs | 13 +------------ 10 files changed, 23 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ICreateReplay.cs diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs index d5c1e9bd15..f705009d18 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Rulesets.EmptyFreeform.Objects; using osu.Game.Rulesets.EmptyFreeform.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -11,7 +10,7 @@ using osu.Game.Users; namespace osu.Game.Rulesets.EmptyFreeform.Mods { - public class EmptyFreeformModAutoplay : ModAutoplay + public class EmptyFreeformModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs index 8ea334c99c..4565c97d1a 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs @@ -4,14 +4,13 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Pippidon.Objects; using osu.Game.Rulesets.Pippidon.Replays; using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Pippidon.Mods { - public class PippidonModAutoplay : ModAutoplay + public class PippidonModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs index 6dad1ff43b..431994e098 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs @@ -3,7 +3,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.EmptyScrolling.Objects; using osu.Game.Rulesets.EmptyScrolling.Replays; using osu.Game.Scoring; using osu.Game.Users; @@ -11,7 +10,7 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.EmptyScrolling.Mods { - public class EmptyScrollingModAutoplay : ModAutoplay + public class EmptyScrollingModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs index 8ea334c99c..4565c97d1a 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs @@ -4,14 +4,13 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Pippidon.Objects; using osu.Game.Rulesets.Pippidon.Replays; using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Pippidon.Mods { - public class PippidonModAutoplay : ModAutoplay + public class PippidonModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index e1eceea606..f1b51e51d0 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -11,7 +10,7 @@ using osu.Game.Users; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModAutoplay : ModAutoplay + public class CatchModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index 105d88129c..6ae854e7f3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -12,7 +11,7 @@ using osu.Game.Users; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModAutoplay : ModAutoplay + public class ManiaModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 3b1f271d41..652da7123e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -6,14 +6,13 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAutoplay : ModAutoplay + public class OsuModAutoplay : ModAutoplay { public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index 64e59b64d0..31d9abf8b2 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -4,14 +4,13 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModAutoplay : ModAutoplay + public class TaikoModAutoplay : ModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game/Rulesets/Mods/ICreateReplay.cs b/osu.Game/Rulesets/Mods/ICreateReplay.cs new file mode 100644 index 0000000000..098bd8799a --- /dev/null +++ b/osu.Game/Rulesets/Mods/ICreateReplay.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 System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Scoring; + +namespace osu.Game.Rulesets.Mods +{ + public interface ICreateReplay + { + public Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods); + } +} diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index d6e1d46b06..b84b5671e1 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -7,22 +7,11 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Replays; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModAutoplay : ModAutoplay, IApplicableToDrawableRuleset - where T : HitObject - { - public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods)); - } - } - - public abstract class ModAutoplay : Mod, IApplicableFailOverride + public abstract class ModAutoplay : Mod, IApplicableFailOverride, ICreateReplay { public override string Name => "Autoplay"; public override string Acronym => "AT"; From c2b938a29f5fef339a3adeb9ab62fa647a06dc12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 18:09:37 +0900 Subject: [PATCH 02/19] Remove autoplay consideration from `Player` --- osu.Game/Screens/Play/Player.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 39f9e2d388..b818dbea08 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -161,9 +161,7 @@ namespace osu.Game.Screens.Play if (!LoadedBeatmapSuccessfully) return; - // replays should never be recorded or played back when autoplay is enabled - if (!Mods.Value.Any(m => m is ModAutoplay)) - PrepareReplay(); + PrepareReplay(); gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } From 7f9318d97658d1dcbce49162c1fa3d7d9bdde4db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 18:36:47 +0900 Subject: [PATCH 03/19] Expose `GameplayBeatmap` to derived `Player` classes --- osu.Game/Screens/Play/Player.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b818dbea08..8e9c2fadcd 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -145,7 +145,7 @@ namespace osu.Game.Screens.Play Configuration = configuration ?? new PlayerConfiguration(); } - private GameplayBeatmap gameplayBeatmap; + protected GameplayBeatmap GameplayBeatmap { get; private set; } private ScreenSuspensionHandler screenSuspension; @@ -221,10 +221,10 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); - AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); + AddInternal(GameplayBeatmap = new GameplayBeatmap(playableBeatmap)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); - dependencies.CacheAs(gameplayBeatmap); + dependencies.CacheAs(GameplayBeatmap); var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); @@ -282,7 +282,7 @@ namespace osu.Game.Screens.Play { HealthProcessor.ApplyResult(r); ScoreProcessor.ApplyResult(r); - gameplayBeatmap.ApplyResult(r); + GameplayBeatmap.ApplyResult(r); }; DrawableRuleset.RevertResult += r => @@ -946,7 +946,7 @@ namespace osu.Game.Screens.Play using (var stream = new MemoryStream()) { - new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream); + new LegacyScoreEncoder(score, GameplayBeatmap.PlayableBeatmap).Encode(stream); replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); } From 7c89dbcd3502e68639476f9d99ab92e63205c870 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 May 2021 18:37:04 +0900 Subject: [PATCH 04/19] Externalise autoplay generation --- osu.Game/Screens/Play/ReplayPlayer.cs | 21 ++++++++++++++++-- osu.Game/Screens/Select/PlaySongSelect.cs | 26 +++++++++++++++-------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index e23cc22929..07c3d197da 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -1,9 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -11,15 +15,28 @@ namespace osu.Game.Screens.Play { public class ReplayPlayer : Player, IKeyBindingHandler { - protected readonly Score Score; + protected Score Score { get; private set; } + + private readonly Func, Score> createScore; // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) protected override bool CheckModsAllowFailure() => false; public ReplayPlayer(Score score, PlayerConfiguration configuration = null) + : this((_, __) => score, configuration) + { + } + + public ReplayPlayer(Func, Score> createScore, PlayerConfiguration configuration = null) : base(configuration) { - Score = score; + this.createScore = createScore; + } + + [BackgroundDependencyLoader] + private void load() + { + Score = createScore(GameplayBeatmap, Mods.Value); } protected override void PrepareReplay() diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index dfb4b59060..357222c109 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Select public class PlaySongSelect : SongSelect { private bool removeAutoModOnResume; - private OsuScreen player; + private OsuScreen playerLoader; [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select { base.OnResuming(last); - player = null; + playerLoader = null; if (removeAutoModOnResume) { @@ -79,14 +79,14 @@ namespace osu.Game.Screens.Select protected override bool OnStart() { - if (player != null) return false; + if (playerLoader != null) return false; // Ctrl+Enter should start map with autoplay enabled. if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) { - var autoplayMod = getAutoplayMod(); + var autoInstance = getAutoplayMod(); - if (autoplayMod == null) + if (autoInstance == null) { notifications?.Post(new SimpleNotification { @@ -97,18 +97,26 @@ namespace osu.Game.Screens.Select var mods = Mods.Value; - if (mods.All(m => m.GetType() != autoplayMod.GetType())) + if (mods.All(m => m.GetType() != autoInstance.GetType())) { - Mods.Value = mods.Append(autoplayMod).ToArray(); + Mods.Value = mods.Append(autoInstance).ToArray(); removeAutoModOnResume = true; } } SampleConfirm?.Play(); - this.Push(player = new PlayerLoader(() => new SoloPlayer())); - + this.Push(playerLoader = new PlayerLoader(createPlayer)); return true; + + Player createPlayer() + { + var autoplayMod = Mods.Value.OfType().FirstOrDefault(); + if (autoplayMod != null) + return new ReplayPlayer((beatmap, mods) => autoplayMod.CreateReplayScore(beatmap, mods)); + + return new SoloPlayer(); + } } } } From 9221213fe527bdc4e33188a1a9a7642edd240ab4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 14:19:13 +0900 Subject: [PATCH 05/19] Fix potential nullref is beatmap load failed --- osu.Game/Screens/Play/ReplayPlayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 07c3d197da..f26675cc64 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -36,6 +36,8 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { + if (!LoadedBeatmapSuccessfully) return; + Score = createScore(GameplayBeatmap, Mods.Value); } From cbf3ef5400bbc7ea2e75f532845175f99a002341 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 14:22:16 +0900 Subject: [PATCH 06/19] Create replay via the `ICreateReplay` interface instead of explicitly `ModAutoplay` --- osu.Game/Screens/Select/PlaySongSelect.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 357222c109..418cf23ce7 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -111,9 +111,9 @@ namespace osu.Game.Screens.Select Player createPlayer() { - var autoplayMod = Mods.Value.OfType().FirstOrDefault(); - if (autoplayMod != null) - return new ReplayPlayer((beatmap, mods) => autoplayMod.CreateReplayScore(beatmap, mods)); + var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); + if (replayGeneratingMod != null) + return new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods)); return new SoloPlayer(); } From 240f7facba9c365d1d3850f314ce2c4e34a24254 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 15:39:02 +0900 Subject: [PATCH 07/19] Add local concessions for autoplay test handling --- osu.Game/Tests/Visual/TestPlayer.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 0addc9de75..99308f8d75 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -2,6 +2,7 @@ // 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.Game.Rulesets.Judgements; @@ -48,6 +49,20 @@ namespace osu.Game.Tests.Visual PauseOnFocusLost = pauseOnFocusLost; } + protected override void PrepareReplay() + { + var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); + + if (replayGeneratingMod != null) + { + // This logic should really not exist (and tests should be instantiating a ReplayPlayer), but a lot of base work is required to make that happen. + DrawableRuleset?.SetReplayScore(replayGeneratingMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value)); + return; + } + + base.PrepareReplay(); + } + [BackgroundDependencyLoader] private void load() { From 3ba0d29108fc250e9c29ea37ec14a9a900741ebf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 15:44:24 +0900 Subject: [PATCH 08/19] Fix incorrect beatmap being parsed down for autoplay generation --- osu.Game/Screens/Play/ReplayPlayer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index f26675cc64..91236ad607 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -17,7 +18,7 @@ namespace osu.Game.Screens.Play { protected Score Score { get; private set; } - private readonly Func, Score> createScore; + private readonly Func, Score> createScore; // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) protected override bool CheckModsAllowFailure() => false; @@ -27,7 +28,7 @@ namespace osu.Game.Screens.Play { } - public ReplayPlayer(Func, Score> createScore, PlayerConfiguration configuration = null) + public ReplayPlayer(Func, Score> createScore, PlayerConfiguration configuration = null) : base(configuration) { this.createScore = createScore; @@ -38,7 +39,7 @@ namespace osu.Game.Screens.Play { if (!LoadedBeatmapSuccessfully) return; - Score = createScore(GameplayBeatmap, Mods.Value); + Score = createScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); } protected override void PrepareReplay() From 790f1dacc9839d0267797821bb59042c884b549d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 16:24:38 +0900 Subject: [PATCH 09/19] Ensure `ScoreProcessor` is still hooked up in special case --- osu.Game/Tests/Visual/TestPlayer.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 99308f8d75..b7e1c68c89 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -51,12 +51,16 @@ namespace osu.Game.Tests.Visual protected override void PrepareReplay() { - var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); + var autoplayMod = Mods.Value.OfType().FirstOrDefault(); - if (replayGeneratingMod != null) + // This logic should really not exist (and tests should be instantiating a ReplayPlayer), but a lot of base work is required to make that happen. + if (autoplayMod != null) { - // This logic should really not exist (and tests should be instantiating a ReplayPlayer), but a lot of base work is required to make that happen. - DrawableRuleset?.SetReplayScore(replayGeneratingMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value)); + var replayScore = autoplayMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); + + DrawableRuleset?.SetReplayScore(replayScore); + + ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(replayScore.ScoreInfo); return; } From 6e861a9b7fcb107e2f12887ee3f4b93e253beedd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 16:24:38 +0900 Subject: [PATCH 10/19] Revert incorrect `ScoreProcessor` change --- osu.Game/Tests/Visual/TestPlayer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index b7e1c68c89..ceb886f9c4 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -56,11 +56,7 @@ namespace osu.Game.Tests.Visual // This logic should really not exist (and tests should be instantiating a ReplayPlayer), but a lot of base work is required to make that happen. if (autoplayMod != null) { - var replayScore = autoplayMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); - - DrawableRuleset?.SetReplayScore(replayScore); - - ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(replayScore.ScoreInfo); + DrawableRuleset?.SetReplayScore(autoplayMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value)); return; } From 7a71cc1e821e0346e1d9c19fc4506388928b9ee3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 16:54:29 +0900 Subject: [PATCH 11/19] Fix actually incorrect navigation test (can no longer retry from autoplay results) --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 253e448bb4..dd05ce9b7e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - AddStep("set autoplay", () => Game.SelectedMods.Value = new[] { new OsuModAutoplay() }); + AddStep("set nofail", () => Game.SelectedMods.Value = new[] { new OsuModNoFail() }); AddStep("press enter", () => InputManager.Key(Key.Enter)); AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); From f14c0eae9956641f8c77179f165b059abc98914d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Jun 2021 17:28:41 +0900 Subject: [PATCH 12/19] Fix editor no longer creating autoplay frames --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index a2dade2627..0667145ffb 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -349,6 +349,9 @@ namespace osu.Game.Rulesets.UI foreach (var mod in mods.OfType>()) mod.ApplyToDrawableRuleset(this); + foreach (var mod in mods.OfType()) + SetReplayScore(mod.CreateReplayScore(Beatmap, mods)); + foreach (var mod in mods.OfType()) mod.ReadFromConfig(config); } From 8a76d97b63a65fe4e303525e4bd09d809cafa2f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 11:06:30 +0900 Subject: [PATCH 13/19] Remove replay logic from `DrawableRuleset` (and implement in `DrawableEditorRulesetWrapper`) --- .../Rulesets/Edit/DrawableEditorRulesetWrapper.cs | 14 +++++++++++--- osu.Game/Rulesets/UI/DrawableRuleset.cs | 12 +----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index 8166e6b8ce..071f01ca00 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; @@ -52,15 +54,21 @@ namespace osu.Game.Rulesets.Edit if (changeHandler != null) { // for now only regenerate replay on a finalised state change, not HitObjectUpdated. - changeHandler.OnStateChange += updateReplay; + changeHandler.OnStateChange += () => Scheduler.AddOnce(regenerateAutoplay); } else { - beatmap.HitObjectUpdated += _ => updateReplay(); + beatmap.HitObjectUpdated += _ => Scheduler.AddOnce(regenerateAutoplay); } + + Scheduler.AddOnce(regenerateAutoplay); } - private void updateReplay() => Scheduler.AddOnce(drawableRuleset.RegenerateAutoplay); + private void regenerateAutoplay() + { + var autoplayMod = drawableRuleset.Mods.OfType().Single(); + drawableRuleset.SetReplayScore(autoplayMod.CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods)); + } private void addHitObject(HitObject hitObject) { diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 0667145ffb..16a411d478 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -182,18 +182,11 @@ namespace osu.Game.Rulesets.UI .WithChild(ResumeOverlay))); } - RegenerateAutoplay(); + applyRulesetMods(Mods, config); loadObjects(cancellationToken ?? default); } - public void RegenerateAutoplay() - { - // for now this is applying mods which aren't just autoplay. - // we'll need to reconsider this flow in the future. - applyRulesetMods(Mods, config); - } - /// /// Creates and adds drawable representations of hit objects to the play field. /// @@ -349,9 +342,6 @@ namespace osu.Game.Rulesets.UI foreach (var mod in mods.OfType>()) mod.ApplyToDrawableRuleset(this); - foreach (var mod in mods.OfType()) - SetReplayScore(mod.CreateReplayScore(Beatmap, mods)); - foreach (var mod in mods.OfType()) mod.ReadFromConfig(config); } From 911256603bf0ba6d83c05694c6d20ecdbafa041b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 11:10:02 +0900 Subject: [PATCH 14/19] Rewrite comment to hopefully be more informative --- osu.Game/Tests/Visual/TestPlayer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index ceb886f9c4..09da4db952 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -51,9 +51,15 @@ namespace osu.Game.Tests.Visual protected override void PrepareReplay() { + // Generally, replay generation is handled by whatever is constructing the player. + // This is implemented locally here to ease migration of test scenes that have some executions + // running with autoplay and some not, but are not written in a way that lends to instantiating + // different `Player` types. + // + // Eventually we will want to remove this and update all test usages which rely on autoplay to use + // a `TestReplayPlayer`. var autoplayMod = Mods.Value.OfType().FirstOrDefault(); - // This logic should really not exist (and tests should be instantiating a ReplayPlayer), but a lot of base work is required to make that happen. if (autoplayMod != null) { DrawableRuleset?.SetReplayScore(autoplayMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value)); From 45984f035b731ff0d39510b22df7375455fafc39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 11:33:43 +0900 Subject: [PATCH 15/19] Make autoplay tests test via the `ReplayPlayer` code path --- .../Visual/Gameplay/TestSceneAutoplay.cs | 4 +- osu.Game/Tests/Visual/TestReplayPlayer.cs | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Tests/Visual/TestReplayPlayer.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index e47c782bca..bc7cf8eee2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -18,12 +18,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Description("Player instantiated with an autoplay mod.")] public class TestSceneAutoplay : TestSceneAllRulesetPlayers { - protected new TestPlayer Player => (TestPlayer)base.Player; + protected new TestReplayPlayer Player => (TestReplayPlayer)base.Player; protected override Player CreatePlayer(Ruleset ruleset) { SelectedMods.Value = new[] { ruleset.GetAutoplayMod() }; - return new TestPlayer(false); + return new TestReplayPlayer(false); } protected override void AddCheckSteps() diff --git a/osu.Game/Tests/Visual/TestReplayPlayer.cs b/osu.Game/Tests/Visual/TestReplayPlayer.cs new file mode 100644 index 0000000000..ac47d186eb --- /dev/null +++ b/osu.Game/Tests/Visual/TestReplayPlayer.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + /// + /// A player that exposes many components that would otherwise not be available, for testing purposes. + /// + public class TestReplayPlayer : ReplayPlayer + { + protected override bool PauseOnFocusLost { get; } + + public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; + + /// + /// Mods from *player* (not OsuScreen). + /// + public new Bindable> Mods => base.Mods; + + public new HUDOverlay HUDOverlay => base.HUDOverlay; + + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public new HealthProcessor HealthProcessor => base.HealthProcessor; + + public new bool PauseCooldownActive => base.PauseCooldownActive; + + public readonly List Results = new List(); + + /// + /// Instantiate a replay player that renders an autoplay mod. + /// + public TestReplayPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) + : base((beatmap, mods) => mods.OfType().First().CreateReplayScore(beatmap, mods), new PlayerConfiguration + { + AllowPause = allowPause, + ShowResults = showResults + }) + { + PauseOnFocusLost = pauseOnFocusLost; + } + + /// + /// Instantiate a replay player that renders the provided replay. + /// + public TestReplayPlayer(Score score, bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) + : base(score, new PlayerConfiguration + { + AllowPause = allowPause, + ShowResults = showResults + }) + { + PauseOnFocusLost = pauseOnFocusLost; + } + + [BackgroundDependencyLoader] + private void load() + { + ScoreProcessor.NewJudgement += r => Results.Add(r); + } + } +} From a15cac6f5386651adf0af91d990f94525da98b99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 15:44:04 +0900 Subject: [PATCH 16/19] Change the way `Score` is initialised in `Player` to better lend to population of metadata --- .../Spectate/MultiSpectatorPlayer.cs | 4 +- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 8 +- osu.Game/Screens/Play/Player.cs | 73 ++++++++----------- osu.Game/Screens/Play/ReplayPlayer.cs | 24 +----- osu.Game/Screens/Play/SpectatorPlayer.cs | 12 +-- 5 files changed, 45 insertions(+), 76 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 0fe9e01d9d..2c157b0564 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -17,7 +17,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public class MultiSpectatorPlayer : SpectatorPlayer { private readonly Bindable waitingOnFrames = new Bindable(true); - private readonly Score score; private readonly ISpectatorPlayerClock spectatorPlayerClock; /// @@ -28,7 +27,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISpectatorPlayerClock spectatorPlayerClock) : base(score) { - this.score = score; this.spectatorPlayerClock = spectatorPlayerClock; } @@ -43,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate 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; + waitingOnFrames.Value = DrawableRuleset.FrameStableClock.WaitingOnFrames.Value || Score.Replay.Frames.Count == 0; } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 260d4961ff..a2ef715367 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -54,11 +54,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return new PlaylistsResultsScreen(score, RoomId.Value.Value, PlaylistItem, true); } - protected override Score CreateScore() + protected override void PrepareScoreForResults() { - var score = base.CreateScore(); - score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); - return score; + base.PrepareScoreForResults(); + + Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 99570f6c5e..0d1ebd30fc 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -24,10 +23,8 @@ using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Overlays; -using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -137,6 +134,8 @@ namespace osu.Game.Screens.Play public readonly PlayerConfiguration Configuration; + protected Score Score { get; private set; } + /// /// Create a new player instance. /// @@ -161,22 +160,32 @@ namespace osu.Game.Screens.Play if (!LoadedBeatmapSuccessfully) return; + Score = CreateScore(); + + // ensure the score is in a consistent state with the current player. + Score.ScoreInfo.Beatmap = Beatmap.Value.BeatmapInfo; + Score.ScoreInfo.Ruleset = rulesetInfo; + Score.ScoreInfo.Mods = Mods.Value.ToArray(); + PrepareReplay(); + ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); + gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } - [CanBeNull] - private Score recordingScore; - /// /// Run any recording / playback setup for replays. /// protected virtual void PrepareReplay() { - DrawableRuleset.SetRecordTarget(recordingScore = new Score()); + DrawableRuleset.SetRecordTarget(Score); + } - ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(recordingScore.ScoreInfo); + protected virtual void PrepareScoreForResults() + { + // perform one final population to ensure everything is up-to-date. + ScoreProcessor.PopulateScore(Score.ScoreInfo); } [BackgroundDependencyLoader(true)] @@ -631,11 +640,11 @@ namespace osu.Game.Screens.Play prepareScoreForDisplayTask ??= Task.Run(async () => { - var score = CreateScore(); + PrepareScoreForResults(); try { - await PrepareScoreForResultsAsync(score).ConfigureAwait(false); + await PrepareScoreForResultsAsync(Score).ConfigureAwait(false); } catch (Exception ex) { @@ -644,14 +653,14 @@ namespace osu.Game.Screens.Play try { - await ImportScore(score).ConfigureAwait(false); + await ImportScore(Score).ConfigureAwait(false); } catch (Exception ex) { Logger.Error(ex, "Score import failed!"); } - return score.ScoreInfo; + return Score.ScoreInfo; }); if (skipStoryboardOutro) @@ -903,41 +912,19 @@ namespace osu.Game.Screens.Play } /// - /// Creates the player's . + /// Creates the player's . /// - /// The . - protected virtual Score CreateScore() - { - var score = new Score + /// The . + protected virtual Score CreateScore() => + new Score { - ScoreInfo = new ScoreInfo - { - Beatmap = Beatmap.Value.BeatmapInfo, - Ruleset = rulesetInfo, - Mods = Mods.Value.ToArray(), - } + ScoreInfo = new ScoreInfo { User = api.LocalUser.Value }, }; - if (DrawableRuleset.ReplayScore != null) - { - score.ScoreInfo.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser(); - score.Replay = DrawableRuleset.ReplayScore.Replay; - } - else - { - score.ScoreInfo.User = api.LocalUser.Value; - score.Replay = new Replay { Frames = recordingScore?.Replay.Frames.ToList() ?? new List() }; - } - - ScoreProcessor.PopulateScore(score.ScoreInfo); - - return score; - } - /// - /// Imports the player's to the local database. + /// Imports the player's to the local database. /// - /// The to import. + /// The to import. /// The imported score. protected virtual async Task ImportScore(Score score) { @@ -968,9 +955,9 @@ namespace osu.Game.Screens.Play } /// - /// Prepare the for display at results. + /// Prepare the for display at results. /// - /// The to prepare. + /// The to prepare. /// A task that prepares the provided score. On completion, the score is assumed to be ready for display. protected virtual Task PrepareScoreForResultsAsync(Score score) => Task.CompletedTask; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 91236ad607..e440e7d34e 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; @@ -16,8 +15,6 @@ namespace osu.Game.Screens.Play { public class ReplayPlayer : Player, IKeyBindingHandler { - protected Score Score { get; private set; } - private readonly Func, Score> createScore; // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) @@ -34,28 +31,15 @@ namespace osu.Game.Screens.Play this.createScore = createScore; } - [BackgroundDependencyLoader] - private void load() - { - if (!LoadedBeatmapSuccessfully) return; - - Score = createScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); - } - protected override void PrepareReplay() { DrawableRuleset?.SetReplayScore(Score); + + // todo: move to base class along with Score? + ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); } - protected override Score CreateScore() - { - var baseScore = base.CreateScore(); - - // Since the replay score doesn't contain statistics, we'll pass them through here. - Score.ScoreInfo.HitEvents = baseScore.ScoreInfo.HitEvents; - - return Score; - } + protected override Score CreateScore() => createScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); // Don't re-import replay scores as they're already present in the database. protected override Task ImportScore(Score score) => Task.CompletedTask; diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index a8125dfded..67471dff90 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -18,6 +18,9 @@ namespace osu.Game.Screens.Play { private readonly Score score; + [Resolved] + private SpectatorClient spectatorClient { get; set; } + protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap public SpectatorPlayer(Score score) @@ -25,13 +28,10 @@ namespace osu.Game.Screens.Play this.score = score; } - protected override ResultsScreen CreateResults(ScoreInfo score) - { - return new SpectatorResultsScreen(score); - } + protected override Score CreateScore() => score; - [Resolved] - private SpectatorClient spectatorClient { get; set; } + protected override ResultsScreen CreateResults(ScoreInfo score) + => new SpectatorResultsScreen(score); [BackgroundDependencyLoader] private void load() From e0eb0adb0af1aa7b30b875fa7aa8e5df756af96d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 18:32:14 +0900 Subject: [PATCH 17/19] Remove unnecessary bind in `ReplayPlayer` --- osu.Game/Screens/Play/ReplayPlayer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index e440e7d34e..f70c05c2ff 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -34,9 +34,6 @@ namespace osu.Game.Screens.Play protected override void PrepareReplay() { DrawableRuleset?.SetReplayScore(Score); - - // todo: move to base class along with Score? - ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); } protected override Score CreateScore() => createScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); From cde8de154d10e5470dac1f974938e861d2b8db72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Jun 2021 19:11:09 +0900 Subject: [PATCH 18/19] Remove unused test property for now --- osu.Game/Tests/Visual/TestReplayPlayer.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Tests/Visual/TestReplayPlayer.cs b/osu.Game/Tests/Visual/TestReplayPlayer.cs index ac47d186eb..da302d018d 100644 --- a/osu.Game/Tests/Visual/TestReplayPlayer.cs +++ b/osu.Game/Tests/Visual/TestReplayPlayer.cs @@ -3,9 +3,7 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -38,8 +36,6 @@ namespace osu.Game.Tests.Visual public new bool PauseCooldownActive => base.PauseCooldownActive; - public readonly List Results = new List(); - /// /// Instantiate a replay player that renders an autoplay mod. /// @@ -65,11 +61,5 @@ namespace osu.Game.Tests.Visual { PauseOnFocusLost = pauseOnFocusLost; } - - [BackgroundDependencyLoader] - private void load() - { - ScoreProcessor.NewJudgement += r => Results.Add(r); - } } } From 66dd7b77055cfe297273148bf308372a025f17a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Jun 2021 14:37:38 +0900 Subject: [PATCH 19/19] Update test logic to allow gameplay to properly continue --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index dd05ce9b7e..0308d74aa4 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; @@ -95,11 +96,12 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - AddStep("set nofail", () => Game.SelectedMods.Value = new[] { new OsuModNoFail() }); + AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } }); AddStep("press enter", () => InputManager.Key(Key.Enter)); AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); - AddStep("seek to end", () => player.ChildrenOfType().First().Seek(beatmap().Track.Length)); + AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning); + AddStep("seek to near end", () => player.ChildrenOfType().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000)); AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded); AddStep("attempt to retry", () => results.ChildrenOfType().First().Action()); AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player);