diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 1d101383cc..6b24ac7384 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -264,13 +264,13 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMutedNotificationMasterVolume() { - addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.IsDefault); + addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.Value == 0.5); } [Test] public void TestMutedNotificationTrackVolume() { - addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.IsDefault); + addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.Value == 0.5); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 247b822dc3..38a091dd85 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -15,8 +15,10 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -101,6 +103,37 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for last played to update", () => getLastPlayed() != null); } + [Test] + public void TestModReferenceNotRetained() + { + AddStep("allow fail", () => allowFail = false); + + Mod[] originalMods = { new OsuModDaycore { SpeedChange = { Value = 0.8 } } }; + Mod[] playerMods = null!; + + AddStep("load player with mods", () => LoadPlayer(originalMods)); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); + + AddStep("get mods at start of gameplay", () => playerMods = Player.Score.ScoreInfo.Mods.ToArray()); + + // Player creates new instance of mods during load. + AddAssert("player score has copied mods", () => playerMods.First(), () => Is.Not.SameAs(originalMods.First())); + AddAssert("player score has matching mods", () => playerMods.First(), () => Is.EqualTo(originalMods.First())); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + + // Player creates new instance of mods after gameplay to ensure any runtime references to drawables etc. are not retained. + AddAssert("results screen score has copied mods", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.Not.SameAs(playerMods.First())); + AddAssert("results screen score has matching", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.EqualTo(playerMods.First())); + + AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); + AddUntilStep("databased score has correct mods", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID)).Mods.First(), () => Is.EqualTo(playerMods.First())); + } + [Test] public void TestScoreStoredLocally() { diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 25a7bad9e8..1b36ae176d 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -137,6 +137,11 @@ namespace osu.Game.Scoring clone.Statistics = new Dictionary(clone.Statistics); clone.MaximumStatistics = new Dictionary(clone.MaximumStatistics); + + // Ensure we have fresh mods to avoid any references (ie. after gameplay). + clone.clearAllMods(); + clone.ModsJson = ModsJson; + clone.RealmUser = new RealmUser { OnlineID = RealmUser.OnlineID, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index 9c05c19d1b..ecef7509d9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -25,9 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { private const double fade_time = 50; - private SpriteIcon icon; - private OsuSpriteText text; - private ProgressBar progressBar; + private SpriteIcon icon = null!; + private OsuSpriteText text = null!; + private ProgressBar progressBar = null!; public StateDisplay() { @@ -86,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants }; } - private OsuColour colours; + private OsuColour colours = null!; public void UpdateStatus(MultiplayerUserState state, BeatmapAvailability availability) { @@ -164,10 +161,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants break; case DownloadState.Downloading: - Debug.Assert(availability.DownloadProgress != null); - progressBar.FadeIn(fade_time); - progressBar.CurrentTime = availability.DownloadProgress.Value; + progressBar.CurrentTime = availability.DownloadProgress ?? 0; text.Text = "downloading map"; icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index b3d5066a9e..d0eb8f8ca1 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -47,10 +47,7 @@ namespace osu.Game.Screens.Play.HUD if (clock != null) gameplayClock = clock; - // Lock height so changes in text autosize (if character height changes) - // don't cause parent invalidation. - Height = 14; - + AutoSizeAxes = Axes.Y; Children = new Drawable[] { new Container diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6373633b5a..e32d3d90be 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -502,7 +502,7 @@ namespace osu.Game.Screens.Play private int restartCount; - private const double volume_requirement = 0.05; + private const double volume_requirement = 0.01; private void showMuteWarningIfNeeded() { @@ -539,10 +539,11 @@ namespace osu.Game.Screens.Play volumeOverlay.IsMuted.Value = false; // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. + // Note that we only restore halfway to ensure the user isn't suddenly overloaded by unexpectedly high volume. if (audioManager.Volume.Value <= volume_requirement) - audioManager.Volume.SetDefault(); + audioManager.Volume.Value = 0.5f; if (audioManager.VolumeTrack.Value <= volume_requirement) - audioManager.VolumeTrack.SetDefault(); + audioManager.VolumeTrack.Value = 0.5f; return true; }; diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 00d6ede3bf..9ac673ae52 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -15,7 +15,6 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; -using osu.Framework.Layout; using osu.Framework.Threading; namespace osu.Game.Screens.Play @@ -24,11 +23,6 @@ namespace osu.Game.Screens.Play { private BufferedContainer columns; - public SquareGraph() - { - AddLayout(layout); - } - public int ColumnCount => columns?.Children.Count ?? 0; private int progress; @@ -57,7 +51,7 @@ namespace osu.Game.Screens.Play if (value == values) return; values = value; - layout.Invalidate(); + graphNeedsUpdate = true; } } @@ -75,21 +69,25 @@ namespace osu.Game.Screens.Play } } - private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize); private ScheduledDelegate scheduledCreate; + private bool graphNeedsUpdate; + + private Vector2 previousDrawSize; + protected override void Update() { base.Update(); - if (values != null && !layout.IsValid) + if (graphNeedsUpdate || (values != null && DrawSize != previousDrawSize)) { columns?.FadeOut(500, Easing.OutQuint).Expire(); scheduledCreate?.Cancel(); scheduledCreate = Scheduler.AddDelayed(RecreateGraph, 500); - layout.Validate(); + previousDrawSize = DrawSize; + graphNeedsUpdate = false; } } diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index a9decbae57..9bad867206 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -7,7 +7,6 @@ using System; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -57,7 +56,9 @@ namespace osu.Game.Tests.Visual protected virtual bool Autoplay => false; - protected void LoadPlayer() + protected void LoadPlayer() => LoadPlayer(Array.Empty()); + + protected void LoadPlayer(Mod[] mods) { var ruleset = CreatePlayerRuleset(); Ruleset.Value = ruleset.RulesetInfo; @@ -65,20 +66,21 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmap(ruleset.RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(beatmap); - SelectedMods.Value = Array.Empty(); + + SelectedMods.Value = mods; if (!AllowFail) { var noFailMod = ruleset.CreateMod(); if (noFailMod != null) - SelectedMods.Value = new[] { noFailMod }; + SelectedMods.Value = SelectedMods.Value.Append(noFailMod).ToArray(); } if (Autoplay) { var mod = ruleset.GetAutoplayMod(); if (mod != null) - SelectedMods.Value = SelectedMods.Value.Concat(mod.Yield()).ToArray(); + SelectedMods.Value = SelectedMods.Value.Append(mod).ToArray(); } Player = CreatePlayer(ruleset);