diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..8be6479043 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Normalize all the line endings +32a74f95a5c80a0ed18e693f13a47522099df5c3 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 d4496a24fd..d3ef3f6e56 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,22 +3,14 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.EmptyFreeform.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; namespace osu.Game.Rulesets.EmptyFreeform.Mods { public class EmptyFreeformModAutoplay : ModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo - { - User = new APIUser { Username = "sample" }, - }, - Replay = new EmptyFreeformAutoGenerator(beatmap).Generate(), - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new EmptyFreeformAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "sample" }); } } 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 6e1fe42ee2..f57b874ff3 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 @@ -3,22 +3,14 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Pippidon.Replays; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Pippidon.Mods { public class PippidonModAutoplay : ModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo - { - User = new APIUser { Username = "sample" }, - }, - Replay = new PippidonAutoGenerator(beatmap).Generate(), - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new PippidonAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "sample" }); } } 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 c5bacb522f..5cf40c30cd 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 @@ -1,24 +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.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.EmptyScrolling.Replays; -using osu.Game.Scoring; using System.Collections.Generic; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.EmptyScrolling.Replays; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.EmptyScrolling.Mods { public class EmptyScrollingModAutoplay : ModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo - { - User = new APIUser { Username = "sample" }, - }, - Replay = new EmptyScrollingAutoGenerator(beatmap).Generate(), - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new EmptyScrollingAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "sample" }); } } 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 6e1fe42ee2..f57b874ff3 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 @@ -3,22 +3,14 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Pippidon.Replays; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Pippidon.Mods { public class PippidonModAutoplay : ModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo - { - User = new APIUser { Username = "sample" }, - }, - Replay = new PippidonAutoGenerator(beatmap).Generate(), - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new PippidonAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "sample" }); } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index 11fffb31de..50e48101d3 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -3,19 +3,14 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Catch.Mods { public class CatchModAutoplay : ModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo { User = new APIUser { Username = "osu!salad" } }, - Replay = new CatchAutoGenerator(beatmap).Generate(), - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new CatchAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "osu!salad" }); } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs index 6d2286b957..7eda6b37d3 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs @@ -3,20 +3,15 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Catch.Mods { public class CatchModCinema : ModCinema { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo { User = new APIUser { Username = "osu!salad" } }, - Replay = new CatchAutoGenerator(beatmap).Generate(), - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new CatchAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "osu!salad" }); } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index 1504c868d0..d444c9b634 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -3,20 +3,15 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModAutoplay : ModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo { User = new APIUser { Username = "osu!topus" } }, - Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), new ModCreatedUser { Username = "osu!topus" }); } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs index 4f1276946b..f0db742eac 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs @@ -3,21 +3,16 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; 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; namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModCinema : ModCinema { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo { User = new APIUser { Username = "osu!topus" } }, - Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), new ModCreatedUser { Username = "osu!topus" }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index 840d871b7b..a9325f98f7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Beatmaps; @@ -13,7 +12,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using osu.Game.Tests.Visual; using osuTK; @@ -67,11 +65,8 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestAutoMod : OsuModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo { User = new APIUser { Username = "Autoplay" } }, - Replay = new MissingAutoGenerator(beatmap, mods).Generate() - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new MissingAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); } private class MissingAutoGenerator : OsuAutoGeneratorBase diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 2668013321..31179cdf4a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -5,10 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Mods { @@ -16,10 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo { User = new APIUser { Username = "Autoplay" } }, - Replay = new OsuAutoGenerator(beatmap, mods).Generate() - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index ff31cfcd18..d677ab43d0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -5,11 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Mods { @@ -17,10 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo { User = new APIUser { Username = "Autoplay" } }, - Replay = new OsuAutoGenerator(beatmap, mods).Generate() - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index 5832ae3dc1..4b74b4991e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -3,19 +3,14 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Replays; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModAutoplay : ModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo { User = new APIUser { Username = "mekkadosu!" } }, - Replay = new TaikoAutoGenerator(beatmap).Generate(), - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new TaikoAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "mekkadosu!" }); } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs index f76e04a069..fee0cb2744 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs @@ -3,20 +3,15 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModCinema : ModCinema { - public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score - { - ScoreInfo = new ScoreInfo { User = new APIUser { Username = "mekkadosu!" } }, - Replay = new TaikoAutoGenerator(beatmap).Generate(), - }; + public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + => new ModReplayData(new TaikoAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "mekkadosu!" }); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index fdc3916c47..346a88a2d5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -5,12 +5,14 @@ using System.ComponentModel; using System.Linq; using osu.Framework.Testing; using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.Break; using osu.Game.Screens.Ranking; +using osu.Game.Users.Drawables; namespace osu.Game.Tests.Visual.Gameplay { @@ -39,11 +41,18 @@ namespace osu.Game.Tests.Visual.Gameplay seekToBreak(1); AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); - AddUntilStep("results displayed", () => getResultsScreen() != null); + + AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true); AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100); AddAssert("score has no misses", () => getResultsScreen().Score.Statistics[HitResult.Miss] == 0); + AddUntilStep("avatar displayed", () => getAvatar() != null); + AddAssert("avatar not clickable", () => getAvatar().ChildrenOfType().First().Action == null); + + ClickableAvatar getAvatar() => getResultsScreen() + .ChildrenOfType().FirstOrDefault(); + ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs index f94e122b30..8622fe8f53 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); - return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap, Array.Empty())); + return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateScoreFromReplayData(beatmap, Array.Empty())); } protected override void AddCheckSteps() diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index 0af77b3b5a..f9c9b2a68b 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -1,16 +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 System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Overlays.Settings.Sections; +using osu.Game.Overlays.Settings.Sections.Input; +using osuTK.Input; namespace osu.Game.Tests.Visual.Settings { [TestFixture] - public class TestSceneSettingsPanel : OsuTestScene + public class TestSceneSettingsPanel : OsuManualInputManagerTestScene { private SettingsPanel settings; private DialogOverlay dialogOverlay; @@ -33,7 +38,55 @@ namespace osu.Game.Tests.Visual.Settings public void ToggleVisibility() { AddWaitStep("wait some", 5); - AddToggleStep("toggle editor visibility", visible => settings.ToggleVisibility()); + AddToggleStep("toggle visibility", visible => settings.ToggleVisibility()); + } + + [Test] + public void TestTextboxFocusAfterNestedPanelBackButton() + { + AddUntilStep("sections loaded", () => settings.SectionsContainer.Children.Count > 0); + AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); + + AddStep("open key binding subpanel", () => + { + settings.SectionsContainer + .ChildrenOfType().FirstOrDefault()? + .ChildrenOfType().FirstOrDefault()? + .TriggerClick(); + }); + + AddUntilStep("binding panel textbox focused", () => settings + .ChildrenOfType().FirstOrDefault()? + .ChildrenOfType().FirstOrDefault()?.HasFocus == true); + + AddStep("Press back", () => settings + .ChildrenOfType().FirstOrDefault()? + .ChildrenOfType().FirstOrDefault()?.TriggerClick()); + + AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); + } + + [Test] + public void TestTextboxFocusAfterNestedPanelEscape() + { + AddUntilStep("sections loaded", () => settings.SectionsContainer.Children.Count > 0); + AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); + + AddStep("open key binding subpanel", () => + { + settings.SectionsContainer + .ChildrenOfType().FirstOrDefault()? + .ChildrenOfType().FirstOrDefault()? + .TriggerClick(); + }); + + AddUntilStep("binding panel textbox focused", () => settings + .ChildrenOfType().FirstOrDefault()? + .ChildrenOfType().FirstOrDefault()?.HasFocus == true); + + AddStep("Escape", () => InputManager.Key(Key.Escape)); + + AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); } [BackgroundDependencyLoader] diff --git a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs index 65753bfe00..4c1256df2e 100644 --- a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.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.IO; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.Rulesets; using osu.Game.Tests; @@ -12,6 +16,45 @@ namespace osu.Game.Tournament.Tests.NonVisual { public class DataLoadTest : TournamentHostTest { + [Test] + public void TestRulesetGetsValidOnlineID() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = new TestTournament(runOnLoadComplete: () => + { + // ReSharper disable once AccessToDisposedClosure + var storage = host.Storage.GetStorageForDirectory(Path.Combine("tournaments", "default")); + + using (var stream = storage.GetStream("bracket.json", FileAccess.Write, FileMode.Create)) + using (var writer = new StreamWriter(stream)) + { + writer.Write(@"{ + ""Ruleset"": { + ""ShortName"": ""taiko"", + ""OnlineID"": -1, + ""Name"": ""osu!taiko"", + ""InstantiationInfo"": ""osu.Game.Rulesets.OsuTaiko.TaikoRuleset, osu.Game.Rulesets.Taiko"", + ""Available"": true + } }"); + } + }); + + LoadTournament(host, osu); + + osu.BracketLoadTask.WaitSafely(); + + Assert.That(osu.Dependencies.Get>().Value.OnlineID, Is.EqualTo(1)); + } + finally + { + host.Exit(); + } + } + } + [Test] public void TestUnavailableRuleset() { @@ -19,7 +62,7 @@ namespace osu.Game.Tournament.Tests.NonVisual { try { - var osu = new TestTournament(); + var osu = new TestTournament(true); LoadTournament(host, osu); var storage = osu.Dependencies.Get(); @@ -35,10 +78,23 @@ namespace osu.Game.Tournament.Tests.NonVisual public class TestTournament : TournamentGameBase { + private readonly bool resetRuleset; + private readonly Action runOnLoadComplete; + + public new Task BracketLoadTask => base.BracketLoadTask; + + public TestTournament(bool resetRuleset = false, Action runOnLoadComplete = null) + { + this.resetRuleset = resetRuleset; + this.runOnLoadComplete = runOnLoadComplete; + } + protected override void LoadComplete() { + runOnLoadComplete?.Invoke(); base.LoadComplete(); - Ruleset.Value = new RulesetInfo(); // not available + if (resetRuleset) + Ruleset.Value = new RulesetInfo(); // not available } } } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index f318c8bd85..a251a043f7 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -64,6 +64,16 @@ namespace osu.Game.Tournament Textures.AddStore(new TextureLoaderStore(new StorageBackedResourceStore(storage))); dependencies.CacheAs(new StableInfo(storage)); + } + + protected override void LoadComplete() + { + MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display + + // we don't want to show the menu cursor as it would appear on stream output. + MenuCursorContainer.Cursor.Alpha = 0; + + base.LoadComplete(); Task.Run(readBracket); } @@ -81,10 +91,14 @@ namespace osu.Game.Tournament ladder ??= new LadderInfo(); - ladder.Ruleset.Value = ladder.Ruleset.Value != null + var resolvedRuleset = ladder.Ruleset.Value != null ? RulesetStore.GetRuleset(ladder.Ruleset.Value.ShortName) : RulesetStore.AvailableRulesets.First(); + // Must set to null initially to avoid the following re-fetch hitting `ShortName` based equality check. + ladder.Ruleset.Value = null; + ladder.Ruleset.Value = resolvedRuleset; + bool addedInfo = false; // assign teams @@ -282,16 +296,6 @@ namespace osu.Game.Tournament } } - protected override void LoadComplete() - { - MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display - - // we don't want to show the menu cursor as it would appear on stream output. - MenuCursorContainer.Cursor.Alpha = 0; - - base.LoadComplete(); - } - protected virtual void SaveChanges() { if (!bracketLoadTaskCompletionSource.Task.IsCompletedSuccessfully) diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 578ff3c618..d3a76a0f1a 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -28,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface if (!allowImmediateFocus) return; - Scheduler.Add(() => GetContainingInputManager().ChangeFocus(this), false); + Scheduler.Add(() => GetContainingInputManager().ChangeFocus(this)); } public new void KillFocus() => base.KillFocus(); diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index a53ac1cd9b..a87f0811a1 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -15,6 +15,11 @@ namespace osu.Game.Online.API.Requests.Responses [JsonObject(MemberSerialization.OptIn)] public class APIUser : IEquatable, IUser { + /// + /// A user ID which can be used to represent any system user which is not attached to a user profile. + /// + public const int SYSTEM_USER_ID = 0; + [JsonProperty(@"id")] public int Id { get; set; } = 1; @@ -238,7 +243,7 @@ namespace osu.Game.Online.API.Requests.Responses /// public static readonly APIUser SYSTEM_USER = new APIUser { - Id = 0, + Id = SYSTEM_USER_ID, Username = "system", Colour = @"9c0101", }; diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index da806c09d3..d55c609d3f 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays protected override bool DimMainContent => false; // dimming is handled by main overlay - private class BackButton : SidebarButton + public class BackButton : SidebarButton { private Container content; diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 9fac1463f2..518a2bf9c8 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays public void ShowUser(IUser user) { - if (user == APIUser.SYSTEM_USER) + if (user.OnlineID == APIUser.SYSTEM_USER_ID) return; Show(); diff --git a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index 071f01ca00..392a5db9da 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Edit private void regenerateAutoplay() { var autoplayMod = drawableRuleset.Mods.OfType().Single(); - drawableRuleset.SetReplayScore(autoplayMod.CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods)); + drawableRuleset.SetReplayScore(autoplayMod.CreateScoreFromReplayData(drawableRuleset.Beatmap, drawableRuleset.Mods)); } private void addHitObject(HitObject hitObject) diff --git a/osu.Game/Rulesets/Mods/ICreateReplay.cs b/osu.Game/Rulesets/Mods/ICreateReplay.cs index 098bd8799a..1e5eeca92c 100644 --- a/osu.Game/Rulesets/Mods/ICreateReplay.cs +++ b/osu.Game/Rulesets/Mods/ICreateReplay.cs @@ -1,14 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public interface ICreateReplay + [Obsolete("Use ICreateReplayData instead")] // Can be removed 20220929 + public interface ICreateReplay : ICreateReplayData { public Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods); + + ModReplayData ICreateReplayData.CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + { + var replayScore = CreateReplayScore(beatmap, mods); + return new ModReplayData(replayScore.Replay, new ModCreatedUser { Username = replayScore.ScoreInfo.User.Username }); + } } } diff --git a/osu.Game/Rulesets/Mods/ICreateReplayData.cs b/osu.Game/Rulesets/Mods/ICreateReplayData.cs new file mode 100644 index 0000000000..7d208e9000 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ICreateReplayData.cs @@ -0,0 +1,63 @@ +// 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.Online.API.Requests.Responses; +using osu.Game.Replays; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// A mod which creates full replay data, which is to be played back in place of a local user playing the game. + /// + public interface ICreateReplayData + { + /// + /// Create replay data. + /// + /// The beatmap to create replay data for. + /// The mods to take into account when creating the replay data. + /// A structure, containing the generated replay data. + /// + /// For callers that want to receive a directly usable instance, + /// the extension method is provided for convenience. + /// + ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods); + } + + /// + /// Data created by a mod that implements . + /// + public class ModReplayData + { + /// + /// The full replay data. + /// + public readonly Replay Replay; + + /// + /// Placeholder user data to show in place of the local user when the associated mod is active. + /// + public readonly ModCreatedUser User; + + public ModReplayData(Replay replay, ModCreatedUser user = null) + { + Replay = replay; + User = user ?? new ModCreatedUser(); + } + } + + /// + /// A user which is associated with a replay that was created by a mod (ie. autoplay or cinema). + /// + public class ModCreatedUser : IUser + { + public int OnlineID => APIUser.SYSTEM_USER_ID; + public bool IsBot => true; + + public string Username { get; set; } = string.Empty; + } +} diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index e804cb6734..87dc627b19 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -11,7 +11,7 @@ using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModAutoplay : Mod, IApplicableFailOverride, ICreateReplay + public abstract class ModAutoplay : Mod, IApplicableFailOverride, ICreateReplayData { public override string Name => "Autoplay"; public override string Acronym => "AT"; @@ -30,6 +30,16 @@ namespace osu.Game.Rulesets.Mods public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; + [Obsolete("Override CreateReplayData(IBeatmap, IReadOnlyList) instead")] // Can be removed 20220929 public virtual Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { Replay = new Replay() }; + + public virtual ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) + { +#pragma warning disable CS0618 + var replayScore = CreateReplayScore(beatmap, mods); +#pragma warning restore CS0618 + + return new ModReplayData(replayScore.Replay, new ModCreatedUser { Username = replayScore.ScoreInfo.User.Username }); + } } } diff --git a/osu.Game/Rulesets/Mods/ModExtensions.cs b/osu.Game/Rulesets/Mods/ModExtensions.cs new file mode 100644 index 0000000000..b22030414b --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModExtensions.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.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; + +namespace osu.Game.Rulesets.Mods +{ + public static class ModExtensions + { + public static Score CreateScoreFromReplayData(this ICreateReplayData mod, IBeatmap beatmap, IReadOnlyList mods) + { + var replayData = mod.CreateReplayData(beatmap, mods); + + return new Score + { + Replay = replayData.Replay, + ScoreInfo = + { + User = new APIUser + { + Id = APIUser.SYSTEM_USER_ID, + Username = replayData.User.Username, + } + } + }; + } + } +} diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index a1f0d22efc..8eeb90a3fd 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -207,6 +207,9 @@ namespace osu.Game.Screens.Menu protected override bool OnKeyDown(KeyDownEvent e) { + if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed || e.SuperPressed) + return false; + if (State == ButtonSystemState.Initial) { if (buttonsTopLevel.Any(b => e.Key == b.TriggerKey)) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs index d467a32acb..49b5b7fed9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs @@ -37,21 +37,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.UserJoined(user); - userJoinedSample?.Play(); + Scheduler.AddOnce(() => userJoinedSample?.Play()); } protected override void UserLeft(MultiplayerRoomUser user) { base.UserLeft(user); - userLeftSample?.Play(); + Scheduler.AddOnce(() => userLeftSample?.Play()); } protected override void UserKicked(MultiplayerRoomUser user) { base.UserKicked(user); - userKickedSample?.Play(); + Scheduler.AddOnce(() => userKickedSample?.Play()); } private void hostChanged(ValueChangedEvent value) @@ -59,7 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // only play sound when the host changes from an already-existing host. if (value.OldValue == null) return; - hostChangedSample?.Play(); + Scheduler.AddOnce(() => hostChangedSample?.Play()); } } } diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 71998622ef..4f5edab526 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -227,15 +227,15 @@ namespace osu.Game.Screens.Play.HUD public int? Team => (User.MatchState as TeamVersusUserState)?.TeamID; - private readonly RulesetInfo ruleset; + private readonly ScoreInfo scoreInfo; public TrackedUserData(MultiplayerRoomUser user, RulesetInfo ruleset, ScoreProcessor scoreProcessor) { - this.ruleset = ruleset; - User = user; ScoreProcessor = scoreProcessor; + scoreInfo = new ScoreInfo { Ruleset = ruleset }; + ScoringMode.BindValueChanged(_ => UpdateScore()); } @@ -253,12 +253,10 @@ namespace osu.Game.Screens.Play.HUD { var header = frame.Header; - Score.Value = ScoreProcessor.ComputePartialScore(ScoringMode.Value, new ScoreInfo - { - Ruleset = ruleset, - MaxCombo = header.MaxCombo, - Statistics = header.Statistics - }); + scoreInfo.MaxCombo = header.MaxCombo; + scoreInfo.Statistics = header.Statistics; + + Score.Value = ScoreProcessor.ComputePartialScore(ScoringMode.Value, scoreInfo); Accuracy.Value = header.Accuracy; CurrentCombo.Value = header.Combo; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 434a0ea030..593436bbb7 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -98,9 +98,12 @@ namespace osu.Game.Screens.Select Player createPlayer() { - var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); + var replayGeneratingMod = Mods.Value.OfType().FirstOrDefault(); + if (replayGeneratingMod != null) - return new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods)); + { + return new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)); + } return new SoloPlayer(); } diff --git a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs index d126eff075..0808cd157f 100644 --- a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osuTK; @@ -94,7 +95,7 @@ namespace osu.Game.Skinning.Editor var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod(); if (replayGeneratingMod != null) - screen.Push(new PlayerLoader(() => new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods)))); + screen.Push(new PlayerLoader(() => new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods)))); }, new[] { typeof(Player), typeof(SongSelect) }) }, } diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index d463905cf4..66a956ca3d 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual if (autoplayMod != null) { - DrawableRuleset?.SetReplayScore(autoplayMod.CreateReplayScore(GameplayState.Beatmap, Mods.Value)); + DrawableRuleset?.SetReplayScore(autoplayMod.CreateScoreFromReplayData(GameplayState.Beatmap, Mods.Value)); return; } diff --git a/osu.Game/Tests/Visual/TestReplayPlayer.cs b/osu.Game/Tests/Visual/TestReplayPlayer.cs index da302d018d..bacb2427b0 100644 --- a/osu.Game/Tests/Visual/TestReplayPlayer.cs +++ b/osu.Game/Tests/Visual/TestReplayPlayer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual /// 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 + : base((beatmap, mods) => mods.OfType().First().CreateScoreFromReplayData(beatmap, mods), new PlayerConfiguration { AllowPause = allowPause, ShowResults = showResults diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 34c87568a1..0dd135b500 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -21,7 +21,7 @@ namespace osu.Game.Users.Drawables /// public bool OpenOnClick { - set => clickableArea.Enabled.Value = value; + set => clickableArea.Enabled.Value = clickableArea.Action != null && value; } /// @@ -52,8 +52,10 @@ namespace osu.Game.Users.Drawables Add(clickableArea = new ClickableArea { RelativeSizeAxes = Axes.Both, - Action = openProfile }); + + if (user?.Id != APIUser.SYSTEM_USER_ID) + clickableArea.Action = openProfile; } [BackgroundDependencyLoader]