diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 265ecb7688..9acf47a67c 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index dbade6ff8d..df5131dd8b 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index a99a93c3e9..bb3e5a66f3 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index bc5d02258f..5625028707 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -13,13 +13,11 @@ using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset + public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); - public bool AllowFail => false; - public void Update(Playfield playfield) { bool requiresHold = false; diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 216cc0222f..5510c3a9d9 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index daee3a520c..ab519360ac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -9,6 +9,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.MathUtils; using osu.Framework.Screens; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -16,12 +17,13 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.PlayerSettings; namespace osu.Game.Tests.Visual.Gameplay { public class TestScenePlayerLoader : ManualInputManagerTestScene { - private PlayerLoader loader; + private TestPlayerLoader loader; private OsuScreenStack stack; [SetUp] @@ -31,19 +33,29 @@ namespace osu.Game.Tests.Visual.Gameplay Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); }); + [Test] + public void TestBlockLoadViaMouseMovement() + { + AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => new TestPlayer(false, false)))); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20); + AddAssert("loader still active", () => loader.IsCurrentScreen()); + AddUntilStep("loads after idle", () => !loader.IsCurrentScreen()); + } + [Test] public void TestLoadContinuation() { Player player = null; SlowLoadPlayer slowPlayer = null; - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => player = new TestPlayer(false, false)))); + AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer(false, false)))); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); AddStep("load slow dummy beatmap", () => { - stack.Push(loader = new PlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + stack.Push(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); }); @@ -61,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load player", () => { Mods.Value = new[] { gameMod = new TestMod() }; - stack.Push(loader = new PlayerLoader(() => player = new TestPlayer())); + stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer())); }); AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); @@ -85,6 +97,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("player mods applied", () => playerMod2.Applied); } + private class TestPlayerLoader : PlayerLoader + { + public new VisualSettings VisualSettings => base.VisualSettings; + + public TestPlayerLoader(Func createPlayer) + : base(createPlayer) + { + } + } + private class TestMod : Mod, IApplicableToScoreProcessor { public override string Name => string.Empty; diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 0655611230..cf8bac7642 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -24,6 +24,7 @@ namespace osu.Game.Tests.Visual.Online typeof(ChangelogListing), typeof(ChangelogSingleBuild), typeof(ChangelogBuild), + typeof(Comments), }; protected override void LoadComplete() diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs index 157e572606..8e358a77db 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs @@ -4,12 +4,9 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -27,8 +24,6 @@ namespace osu.Game.Tests.Visual.SongSelect typeof(RetrievalFailurePlaceholder), }; - private RulesetStore rulesets; - private readonly FailableLeaderboard leaderboard; public TestSceneLeaderboard() @@ -47,13 +42,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable)); - AddStep(@"Real beatmap", realBeatmap); - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; + foreach (BeatmapSetOnlineStatus status in Enum.GetValues(typeof(BeatmapSetOnlineStatus))) + AddStep($"{status} beatmap", () => showBeatmapWithStatus(status)); } private void newScores() @@ -245,34 +235,12 @@ namespace osu.Game.Tests.Visual.SongSelect leaderboard.Scores = scores; } - private void realBeatmap() + private void showBeatmapWithStatus(BeatmapSetOnlineStatus status) { leaderboard.Beatmap = new BeatmapInfo { - StarDifficulty = 1.36, - Version = @"BASIC", OnlineBeatmapID = 1113057, - Ruleset = rulesets.GetRuleset(0), - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 6.5f, - OverallDifficulty = 6.5f, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 115000, - CircleCount = 265, - SliderCount = 71, - PlayCount = 47906, - PassCount = 19899, - }, - Metrics = new BeatmapMetrics - { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, + Status = status, }; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs index c8cc864089..f0e1c38525 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; using osu.Game.Screens.Menu; +using osuTK; using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface @@ -23,11 +24,12 @@ namespace osu.Game.Tests.Visual.UserInterface typeof(Button) }; - public TestSceneButtonSystem() - { - OsuLogo logo; - ButtonSystem buttons; + private OsuLogo logo; + private ButtonSystem buttons; + [SetUp] + public void SetUp() => Schedule(() => + { Children = new Drawable[] { new Box @@ -36,13 +38,47 @@ namespace osu.Game.Tests.Visual.UserInterface RelativeSizeAxes = Axes.Both, }, buttons = new ButtonSystem(), - logo = new OsuLogo { RelativePositionAxes = Axes.Both } + logo = new OsuLogo + { + RelativePositionAxes = Axes.Both, + Position = new Vector2(0.5f) + } }; buttons.SetOsuLogo(logo); + }); + [Test] + public void TestAllStates() + { foreach (var s in Enum.GetValues(typeof(ButtonSystemState)).OfType().Skip(1)) AddStep($"State to {s}", () => buttons.State = s); + + AddStep("Enter mode", performEnterMode); + + AddStep("Return to menu", () => + { + buttons.State = ButtonSystemState.Play; + buttons.FadeIn(MainMenu.FADE_IN_DURATION, Easing.OutQuint); + buttons.MoveTo(new Vector2(0), MainMenu.FADE_IN_DURATION, Easing.OutQuint); + logo.FadeColour(Color4.White, 100, Easing.OutQuint); + logo.FadeIn(100, Easing.OutQuint); + }); + } + + [Test] + public void TestSmoothExit() + { + AddStep("Enter mode", performEnterMode); + } + + private void performEnterMode() + { + buttons.State = ButtonSystemState.EnteringMode; + buttons.FadeOut(MainMenu.FADE_OUT_DURATION, Easing.InSine); + buttons.MoveTo(new Vector2(-800, 0), MainMenu.FADE_OUT_DURATION, Easing.InSine); + logo.FadeOut(300, Easing.InSine) + .ScaleTo(0.2f, 300, Easing.InSine); } } } diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 11d70ee7be..659f5415c3 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 1c169184fb..dad2fe0877 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,9 +5,9 @@ - + - + WinExe diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 1fd3502799..30346a8a96 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -35,15 +35,15 @@ namespace osu.Game.Beatmaps.Drawables protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay); + protected override double TransformDuration => 400; + protected override Drawable CreateDrawable(BeatmapInfo model) { - Drawable drawable = getDrawableForModel(model); - + var drawable = getDrawableForModel(model); drawable.RelativeSizeAxes = Axes.Both; drawable.Anchor = Anchor.Centre; drawable.Origin = Anchor.Centre; drawable.FillMode = FillMode.Fill; - drawable.OnLoadComplete += d => d.FadeInFromZero(400); return drawable; } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 795f0b43f7..1da7c7ec1d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -77,6 +77,7 @@ namespace osu.Game.Configuration Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); Set(OsuSetting.ShowInterface, true); + Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.FloatingComments, false); @@ -131,6 +132,7 @@ namespace osu.Game.Configuration KeyOverlay, FloatingComments, ShowInterface, + ShowHealthDisplayWhenCantFail, MouseDisableButtons, MouseDisableWheel, AudioOffset, diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index e56df05570..6b0e680eb5 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -5,8 +5,10 @@ using System; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Screens.Select.Leaderboards; -using osu.Framework.IO.Network; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Mods; +using System.Text; +using System.Collections.Generic; namespace osu.Game.Online.API.Requests { @@ -15,8 +17,9 @@ namespace osu.Game.Online.API.Requests private readonly BeatmapInfo beatmap; private readonly BeatmapLeaderboardScope scope; private readonly RulesetInfo ruleset; + private readonly IEnumerable mods; - public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global) + public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) { if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); @@ -27,6 +30,7 @@ namespace osu.Game.Online.API.Requests this.beatmap = beatmap; this.scope = scope; this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); + this.mods = mods ?? Array.Empty(); Success += onSuccess; } @@ -40,17 +44,19 @@ namespace osu.Game.Online.API.Requests } } - protected override WebRequest CreateWebRequest() + protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores{createQueryParameters()}"; + + private string createQueryParameters() { - var req = base.CreateWebRequest(); + StringBuilder query = new StringBuilder(@"?"); - req.Timeout = 30000; - req.AddParameter(@"type", scope.ToString().ToLowerInvariant()); - req.AddParameter(@"mode", ruleset.ShortName); + query.Append($@"type={scope.ToString().ToLowerInvariant()}"); + query.Append($@"&mode={ruleset.ShortName}"); - return req; + foreach (var mod in mods) + query.Append($@"&mods[]={mod.Acronym}"); + + return query.ToString(); } - - protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogBuild.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogBuild.cs index 36407c7b0e..56005e15f8 100644 --- a/osu.Game/Online/API/Requests/Responses/APIChangelogBuild.cs +++ b/osu.Game/Online/API/Requests/Responses/APIChangelogBuild.cs @@ -33,6 +33,8 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("versions")] public VersionNavigation Versions { get; set; } + public string Url => $"https://osu.ppy.sh/home/changelog/{UpdateStream.Name}/{Version}"; + public class VersionNavigation { [JsonProperty("next")] diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index 36ae5a756c..44552b214f 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -58,7 +58,11 @@ namespace osu.Game.Overlays.Changelog } if (build != null) - Child = new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild }; + Children = new Drawable[] + { + new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild }, + new Comments(build) + }; } public class ChangelogBuildWithNavigation : ChangelogBuild diff --git a/osu.Game/Overlays/Changelog/Comments.cs b/osu.Game/Overlays/Changelog/Comments.cs new file mode 100644 index 0000000000..4cf39e7b44 --- /dev/null +++ b/osu.Game/Overlays/Changelog/Comments.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Changelog +{ + public class Comments : CompositeDrawable + { + private readonly APIChangelogBuild build; + + public Comments(APIChangelogBuild build) + { + this.build = build; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding + { + Horizontal = 50, + Vertical = 20, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + LinkFlowContainer text; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreyVioletDarker + }, + }, + text = new LinkFlowContainer(t => + { + t.Colour = colours.PinkLighter; + t.Font = OsuFont.Default.With(size: 14); + }) + { + Padding = new MarginPadding(20), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + }; + + text.AddParagraph("Got feedback?", t => + { + t.Colour = Color4.White; + t.Font = OsuFont.Default.With(italics: true, size: 20); + t.Padding = new MarginPadding { Bottom = 20 }; + }); + + text.AddParagraph("We would love to hear what you think of this update! "); + text.AddIcon(FontAwesome.Regular.GrinHearts); + + text.AddParagraph("Please visit the "); + text.AddLink("web version", $"{build.Url}#comments"); + text.AddText(" of this changelog to leave any comments."); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 997d1354b3..9142492610 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -35,6 +35,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Bindable = config.GetBindable(OsuSetting.ShowInterface) }, new SettingsCheckbox + { + LabelText = "Show health display even when you can't fail", + Bindable = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), + }, + new SettingsCheckbox { LabelText = "Always show key overlay", Bindable = config.GetBindable(OsuSetting.KeyOverlay) diff --git a/osu.Game/Rulesets/Mods/IApplicableToHUD.cs b/osu.Game/Rulesets/Mods/IApplicableToHUD.cs new file mode 100644 index 0000000000..4fb535a0b3 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToHUD.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that apply changes to the . + /// + public interface IApplicableToHUD : IApplicableMod + { + /// + /// Provide a . Called once on initialisation of a play instance. + /// + void ApplyToHUD(HUDOverlay overlay); + } +} diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs new file mode 100644 index 0000000000..26efc3932d --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModBlockFail : Mod, IApplicableFailOverride, IApplicableToHUD, IReadFromConfig + { + private Bindable showHealthBar; + + /// + /// We never fail, 'yo. + /// + public bool AllowFail => false; + + public void ReadFromConfig(OsuConfigManager config) + { + showHealthBar = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail); + } + + public void ApplyToHUD(HUDOverlay overlay) + { + overlay.ShowHealthbar.BindTo(showHealthBar); + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 1ee1f92d8c..49ee3354c3 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModNoFail : Mod, IApplicableFailOverride + public abstract class ModNoFail : ModBlockFail { public override string Name => "No Fail"; public override string Acronym => "NF"; @@ -17,10 +17,5 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0.5; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) }; - - /// - /// We never fail, 'yo. - /// - public bool AllowFail => false; } } diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index 4feb89186c..7c355577d4 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModRelax : Mod + public abstract class ModRelax : ModBlockFail { public override string Name => "Relax"; public override string Acronym => "RX"; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 5aa244cc06..1a3e1213b4 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -332,7 +332,7 @@ namespace osu.Game.Screens.Menu break; case ButtonSystemState.EnteringMode: - logoTrackingContainer.StartTracking(logo, 0, Easing.In); + logoTrackingContainer.StartTracking(logo, lastState == ButtonSystemState.Initial ? MainMenu.FADE_OUT_DURATION : 0, Easing.InSine); break; } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 7e6de54d1b..c64bea840f 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -23,7 +23,9 @@ namespace osu.Game.Screens.Menu { public class MainMenu : OsuScreen { - private ButtonSystem buttons; + public const float FADE_IN_DURATION = 300; + + public const float FADE_OUT_DURATION = 400; public override bool HideOverlaysOnEnter => buttons == null || buttons.State == ButtonSystemState.Initial; @@ -35,6 +37,8 @@ namespace osu.Game.Screens.Menu private MenuSideFlashes sideFlashes; + private ButtonSystem buttons; + [Resolved] private GameHost host { get; set; } @@ -141,12 +145,10 @@ namespace osu.Game.Screens.Menu { buttons.State = ButtonSystemState.TopLevel; - const float length = 300; + this.FadeIn(FADE_IN_DURATION, Easing.OutQuint); + this.MoveTo(new Vector2(0, 0), FADE_IN_DURATION, Easing.OutQuint); - this.FadeIn(length, Easing.OutQuint); - this.MoveTo(new Vector2(0, 0), length, Easing.OutQuint); - - sideFlashes.Delay(length).FadeIn(64, Easing.InQuint); + sideFlashes.Delay(FADE_IN_DURATION).FadeIn(64, Easing.InQuint); } } @@ -171,12 +173,10 @@ namespace osu.Game.Screens.Menu { base.OnSuspending(next); - const float length = 400; - buttons.State = ButtonSystemState.EnteringMode; - this.FadeOut(length, Easing.InSine); - this.MoveTo(new Vector2(-800, 0), length, Easing.InSine); + this.FadeOut(FADE_OUT_DURATION, Easing.InSine); + this.MoveTo(new Vector2(-800, 0), FADE_OUT_DURATION, Easing.InSine); sideFlashes.FadeOut(64, Easing.OutQuint); } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 017bf70133..43b9491750 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -23,7 +23,8 @@ namespace osu.Game.Screens.Play { public class HUDOverlay : Container { - private const int duration = 100; + private const int duration = 250; + private const Easing easing = Easing.OutQuint; public readonly KeyCounterDisplay KeyCounter; public readonly RollingCounter ComboCounter; @@ -35,6 +36,8 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + public Bindable ShowHealthbar = new Bindable(true); + private readonly ScoreProcessor scoreProcessor; private readonly DrawableRuleset drawableRuleset; private readonly IReadOnlyList mods; @@ -47,6 +50,8 @@ namespace osu.Game.Screens.Play public Action RequestSeek; + private readonly Container topScoreContainer; + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { this.scoreProcessor = scoreProcessor; @@ -62,11 +67,10 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Container + topScoreContainer = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Y = 30, AutoSizeAxes = Axes.Both, AutoSizeDuration = 200, AutoSizeEasing = Easing.Out, @@ -113,8 +117,21 @@ namespace osu.Game.Screens.Play ModDisplay.Current.Value = mods; showHud = config.GetBindable(OsuSetting.ShowInterface); - showHud.ValueChanged += visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration); - showHud.TriggerChange(); + showHud.BindValueChanged(visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration, easing), true); + + ShowHealthbar.BindValueChanged(healthBar => + { + if (healthBar.NewValue) + { + HealthDisplay.FadeIn(duration, easing); + topScoreContainer.MoveToY(30, duration, easing); + } + else + { + HealthDisplay.FadeOut(duration, easing); + topScoreContainer.MoveToY(0, duration, easing); + } + }, true); if (!showHud.Value && !hasShownNotificationOnce) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 30a3bc2fc6..0da9c77f25 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -498,6 +498,9 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Restart(); GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); + + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToHUD(HUDOverlay); } public override void OnSuspending(IScreen next) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 681ce701d0..5396321160 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Input; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.HUD; @@ -53,6 +54,8 @@ namespace osu.Game.Screens.Play private InputManager inputManager; + private IdleTracker idleTracker; + public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -93,7 +96,8 @@ namespace osu.Game.Screens.Play VisualSettings = new VisualSettings(), new InputSettings() } - } + }, + idleTracker = new IdleTracker(750) }); loadNewPlayer(); @@ -193,7 +197,7 @@ namespace osu.Game.Screens.Play // Here because IsHovered will not update unless we do so. public override bool HandlePositionalInput => true; - private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null; + private bool readyForPush => player.LoadState == LoadState.Ready && (IsHovered || idleTracker.IsIdle.Value) && inputManager?.DraggedDrawable == null; private void pushWhenLoaded() { diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 477037355c..b66a2ffe0f 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.X, OnFilter = (tab, mods) => { + Leaderboard.FilterMods = mods; + switch (tab) { case BeatmapDetailTab.Details: diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 68f153a97d..0f6d4f3188 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -11,6 +11,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; namespace osu.Game.Screens.Select.Leaderboards @@ -36,12 +37,34 @@ namespace osu.Game.Screens.Select.Leaderboards } } + private bool filterMods; + + /// + /// Whether to apply the game's currently selected mods as a filter when retrieving scores. + /// + public bool FilterMods + { + get => filterMods; + set + { + if (value == filterMods) + return; + + filterMods = value; + + UpdateScores(); + } + } + [Resolved] private ScoreManager scoreManager { get; set; } [Resolved] private IBindable ruleset { get; set; } + [Resolved] + private IBindable> mods { get; set; } + [Resolved] private IAPIProvider api { get; set; } @@ -49,16 +72,36 @@ namespace osu.Game.Screens.Select.Leaderboards private void load() { ruleset.ValueChanged += _ => UpdateScores(); + mods.ValueChanged += _ => + { + if (filterMods) + UpdateScores(); + }; } protected override APIRequest FetchScores(Action> scoresCallback) { if (Scope == BeatmapLeaderboardScope.Local) { - Scores = scoreManager - .QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID && s.Ruleset.ID == ruleset.Value.ID) - .OrderByDescending(s => s.TotalScore).ToArray(); + var scores = scoreManager + .QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID && s.Ruleset.ID == ruleset.Value.ID); + + if (filterMods && !mods.Value.Any()) + { + // we need to filter out all scores that have any mods to get all local nomod scores + scores = scores.Where(s => !s.Mods.Any()); + } + else if (filterMods) + { + // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) + // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself + var selectedMods = mods.Value.Select(m => m.Acronym); + scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); + } + + Scores = scores.OrderByDescending(s => s.TotalScore).ToArray(); PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + return null; } @@ -68,19 +111,27 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - if (Beatmap?.OnlineBeatmapID == null) + if (Beatmap?.OnlineBeatmapID == null || Beatmap?.Status <= BeatmapSetOnlineStatus.Pending) { PlaceholderState = PlaceholderState.Unavailable; return null; } - if (Scope != BeatmapLeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) + if (!api.LocalUser.Value.IsSupporter && (Scope != BeatmapLeaderboardScope.Global || filterMods)) { PlaceholderState = PlaceholderState.NotSupporter; return null; } - var req = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope); + IReadOnlyList requestMods = null; + + if (filterMods && !mods.Value.Any()) + // add nomod for the request + requestMods = new Mod[] { new ModNoMod() }; + else if (filterMods) + requestMods = mods.Value; + + var req = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope, requestMods); req.Success += r => scoresCallback?.Invoke(r.Scores);