diff --git a/osu.Game/Overlays/Mods/IncompatibleIcon.cs b/osu.Game/Overlays/Mods/IncompatibleIcon.cs new file mode 100644 index 0000000000..df134fe4a4 --- /dev/null +++ b/osu.Game/Overlays/Mods/IncompatibleIcon.cs @@ -0,0 +1,64 @@ +// 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.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Mods +{ + public class IncompatibleIcon : VisibilityContainer, IHasTooltip + { + private Circle circle; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Size = new Vector2(20); + + State.Value = Visibility.Hidden; + Alpha = 0; + + InternalChildren = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray4, + }, + new SpriteIcon + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(0.6f), + Icon = FontAwesome.Solid.Slash, + Colour = Color4.White, + Shadow = true, + } + }; + } + + protected override void PopIn() + { + this.FadeIn(200, Easing.OutQuint); + circle.FlashColour(Color4.Red, 500, Easing.OutQuint); + this.ScaleTo(1.8f).Then().ScaleTo(1, 500, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(200, Easing.OutQuint); + this.ScaleTo(0.8f, 200, Easing.In); + } + + public LocalisableString TooltipText => "Incompatible with current selected mods"; + } +} diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 572ff0d1aa..4675eb6bc8 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -11,24 +11,29 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System; +using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Utils; namespace osu.Game.Overlays.Mods { /// /// Represents a clickable button which can cycle through one of more mods. /// - public class ModButton : ModButtonEmpty, IHasTooltip + public class ModButton : ModButtonEmpty, IHasCustomTooltip { private ModIcon foregroundIcon; private ModIcon backgroundIcon; private readonly SpriteText text; private readonly Container iconsContainer; + private readonly CompositeDrawable incompatibleIcon; /// /// Fired when the selection changes. @@ -43,6 +48,9 @@ namespace osu.Game.Overlays.Mods // A selected index of -1 means not selected. private int selectedIndex = -1; + [Resolved] + private Bindable> selectedMods { get; set; } + /// /// Change the selected mod index of this button. /// @@ -237,6 +245,23 @@ namespace osu.Game.Overlays.Mods foregroundIcon.Mod = mod; text.Text = mod.Name; Colour = mod.HasImplementation ? Color4.White : Color4.Gray; + + Scheduler.AddOnce(updateCompatibility); + } + + private void updateCompatibility() + { + var m = SelectedMod ?? Mods.First(); + + bool isIncompatible = false; + + if (selectedMods.Value.Count > 0 && !selectedMods.Value.Contains(m)) + isIncompatible = !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(m)); + + if (isIncompatible) + incompatibleIcon.Show(); + else + incompatibleIcon.Hide(); } private void createIcons() @@ -284,11 +309,20 @@ namespace osu.Game.Overlays.Mods { scaleContainer = new Container { - Child = iconsContainer = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, + iconsContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + }, + incompatibleIcon = new IncompatibleIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.BottomRight, + Position = new Vector2(-13), + } }, RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, @@ -305,8 +339,18 @@ namespace osu.Game.Overlays.Mods }, new HoverSounds() }; - Mod = mod; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateCompatibility), true); + } + + public ITooltip GetCustomTooltip() => new ModButtonTooltip(); + + public object TooltipContent => SelectedMod ?? Mods.FirstOrDefault(); } } diff --git a/osu.Game/Overlays/Mods/ModButtonTooltip.cs b/osu.Game/Overlays/Mods/ModButtonTooltip.cs new file mode 100644 index 0000000000..666ed07e28 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModButtonTooltip.cs @@ -0,0 +1,115 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class ModButtonTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText descriptionText; + private readonly Box background; + private readonly OsuSpriteText incompatibleText; + + private readonly Bindable> incompatibleMods = new Bindable>(); + + [Resolved] + private Bindable ruleset { get; set; } + + public ModButtonTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, + Children = new Drawable[] + { + descriptionText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Margin = new MarginPadding { Bottom = 5 } + }, + incompatibleText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Regular), + Text = "Incompatible with:" + }, + new ModDisplay + { + Current = incompatibleMods, + ExpansionMode = ExpansionMode.AlwaysExpanded, + Scale = new Vector2(0.7f) + } + } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray3; + descriptionText.Colour = colours.BlueLighter; + incompatibleText.Colour = colours.BlueLight; + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + + private Mod lastMod; + + public bool SetContent(object content) + { + if (!(content is Mod mod)) + return false; + + if (mod.Equals(lastMod)) return true; + + lastMod = mod; + + descriptionText.Text = mod.Description; + + var incompatibleTypes = mod.IncompatibleMods; + + var allMods = ruleset.Value.CreateInstance().GetAllMods(); + + incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList(); + + if (!incompatibleMods.Value.Any()) + { + incompatibleText.Text = "Compatible with all mods"; + return true; + } + + incompatibleText.Text = "Incompatible with:"; + + return true; + } + + public void Move(Vector2 pos) => Position = pos; + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 96eba7808f..fdef48d556 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -74,6 +74,7 @@ namespace osu.Game.Overlays.Mods protected readonly ModSettingsContainer ModSettingsContainer; + [Cached] public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); private Bindable>> availableMods;