diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index ab6bd78ece..31550a8105 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills { private const double individual_decay_base = 0.125; private const double overall_decay_base = 0.30; + private const double release_threshold = 24; protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 1; @@ -37,31 +38,43 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills var maniaCurrent = (ManiaDifficultyHitObject)current; double endTime = maniaCurrent.EndTime; int column = maniaCurrent.BaseObject.Column; + double closestEndTime = Math.Abs(endTime - maniaCurrent.LastObject.StartTime); // Lowest value we can assume with the current information double holdFactor = 1.0; // Factor to all additional strains in case something else is held double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly + bool isOverlapping = false; // Fill up the holdEndTimes array for (int i = 0; i < holdEndTimes.Length; ++i) { - // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... - if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1)) - holdAddition = 1.0; - - // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1 - if (Precision.AlmostEquals(endTime, holdEndTimes[i], 1)) - holdAddition = 0; + // The current note is overlapped if a previous note or end is overlapping the current note body + isOverlapping |= Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1); // We give a slight bonus to everything if something is held meanwhile if (Precision.DefinitelyBigger(holdEndTimes[i], endTime, 1)) holdFactor = 1.25; + closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - holdEndTimes[i])); + // Decay individual strains individualStrains[i] = applyDecay(individualStrains[i], current.DeltaTime, individual_decay_base); } holdEndTimes[column] = endTime; + // The hold addition is given if there was an overlap, however it is only valid if there are no other note with a similar ending. + // Releasing multiple notes is just as easy as releasing 1. Nerfs the hold addition by half if the closest release is release_threshold away. + // holdAddition + // ^ + // 1.0 + - - - - - -+----------- + // | / + // 0.5 + - - - - -/ Sigmoid Curve + // | /| + // 0.0 +--------+-+---------------> Release Difference / ms + // release_threshold + if (isOverlapping) + holdAddition = 1 / (1 + Math.Exp(0.5 * (release_threshold - closestEndTime))); + // Increase individual strain in own column individualStrains[column] += 2.0 * holdFactor; individualStrain = individualStrains[column]; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedToggleButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs similarity index 63% rename from osu.Game.Tests/Visual/UserInterface/TestSceneShearedToggleButton.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs index b5109aa58d..5a4eeef4d9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedToggleButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs @@ -11,11 +11,62 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneShearedToggleButton : OsuManualInputManagerTestScene + public class TestSceneShearedButtons : OsuManualInputManagerTestScene { [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + [TestCase(false)] + [TestCase(true)] + public void TestShearedButton(bool bigButton) + { + ShearedButton button = null; + bool actionFired = false; + + AddStep("create button", () => + { + actionFired = false; + + if (bigButton) + { + Child = button = new ShearedButton(400) + { + LighterColour = Colour4.FromHex("#FFFFFF"), + DarkerColour = Colour4.FromHex("#FFCC22"), + TextColour = Colour4.Black, + TextSize = 36, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Let's GO!", + Height = 80, + Action = () => actionFired = true, + }; + } + else + { + Child = button = new ShearedButton(200) + { + LighterColour = Colour4.FromHex("#FF86DD"), + DarkerColour = Colour4.FromHex("#DE31AE"), + TextColour = Colour4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Press me", + Height = 80, + Action = () => actionFired = true, + }; + } + }); + + AddStep("set disabled", () => button.Enabled.Value = false); + AddStep("press button", () => button.TriggerClick()); + AddAssert("action not fired", () => !actionFired); + + AddStep("set enabled", () => button.Enabled.Value = true); + AddStep("press button", () => button.TriggerClick()); + AddAssert("action fired", () => actionFired); + } + [Test] public void TestShearedToggleButton() { diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs new file mode 100644 index 0000000000..c3c566782f --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -0,0 +1,197 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class ShearedButton : OsuClickableContainer + { + public LocalisableString Text + { + get => text.Text; + set => text.Text = value; + } + + public float TextSize + { + get => text.Font.Size; + set => text.Font = OsuFont.TorusAlternate.With(size: value); + } + + public Colour4 DarkerColour + { + set + { + darkerColour = value; + Scheduler.AddOnce(updateState); + } + } + + public Colour4 LighterColour + { + set + { + lighterColour = value; + Scheduler.AddOnce(updateState); + } + } + + public Colour4 TextColour + { + set + { + textColour = value; + Scheduler.AddOnce(updateState); + } + } + + [Resolved] + protected OverlayColourProvider ColourProvider { get; private set; } = null!; + + private readonly Box background; + private readonly OsuSpriteText text; + + private const float shear = 0.2f; + + private Colour4? darkerColour; + private Colour4? lighterColour; + private Colour4? textColour; + + private readonly Box flashLayer; + + /// + /// Creates a new + /// + /// + /// The width of the button. + /// + /// If a non- value is provided, this button will have a fixed width equal to the provided value. + /// If a value is provided (or the argument is omitted entirely), the button will autosize in width to fit the text. + /// + /// + public ShearedButton(float? width = null) + { + Height = 50; + Padding = new MarginPadding { Horizontal = shear * 50 }; + + Content.CornerRadius = 7; + Content.Shear = new Vector2(shear, 0); + Content.Masking = true; + Content.BorderThickness = 2; + Content.Anchor = Content.Origin = Anchor.Centre; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.TorusAlternate.With(size: 17), + Shear = new Vector2(-shear, 0) + }, + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White.Opacity(0.9f), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + }; + + if (width != null) + { + Width = width.Value; + } + else + { + AutoSizeAxes = Axes.X; + text.Margin = new MarginPadding { Horizontal = 15 }; + } + } + + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet); + + protected override void LoadComplete() + { + base.LoadComplete(); + + Enabled.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + + updateState(); + FinishTransforms(true); + } + + protected override bool OnClick(ClickEvent e) + { + if (Enabled.Value) + flashLayer.FadeOutFromOne(800, Easing.OutQuint); + + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + Scheduler.AddOnce(updateState); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + Scheduler.AddOnce(updateState); + base.OnHoverLost(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + Content.ScaleTo(0.8f, 2000, Easing.OutQuint); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + Content.ScaleTo(1, 1000, Easing.OutElastic); + base.OnMouseUp(e); + } + + private void updateState() + { + var colourDark = darkerColour ?? ColourProvider.Background3; + var colourLight = lighterColour ?? ColourProvider.Background1; + var colourText = textColour ?? ColourProvider.Content1; + + if (!Enabled.Value) + { + colourDark = colourDark.Darken(0.3f); + colourLight = colourLight.Darken(0.3f); + } + else if (IsHovered) + { + colourDark = colourDark.Lighten(0.2f); + colourLight = colourLight.Lighten(0.2f); + } + + background.FadeColour(colourDark, 150, Easing.OutQuint); + Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(colourDark, colourLight), 150, Easing.OutQuint); + + if (!Enabled.Value) + colourText = colourText.Opacity(0.6f); + + text.FadeColour(colourText, 150, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index aed3be20a0..4780270f66 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs @@ -8,39 +8,18 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Framework.Localisation; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; -using osuTK; namespace osu.Game.Graphics.UserInterface { - public class ShearedToggleButton : OsuClickableContainer + public class ShearedToggleButton : ShearedButton { - public BindableBool Active { get; } = new BindableBool(); - - public LocalisableString Text - { - get => text.Text; - set => text.Text = value; - } - - private readonly Box background; - private readonly OsuSpriteText text; - private Sample? sampleOff; private Sample? sampleOn; - private const float shear = 0.2f; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; + /// + /// Whether this button is currently toggled to an active state. + /// + public BindableBool Active { get; } = new BindableBool(); /// /// Creates a new @@ -53,40 +32,8 @@ namespace osu.Game.Graphics.UserInterface /// /// public ShearedToggleButton(float? width = null) + : base(width) { - Height = 50; - Padding = new MarginPadding { Horizontal = shear * 50 }; - - Content.CornerRadius = 7; - Content.Shear = new Vector2(shear, 0); - Content.Masking = true; - Content.BorderThickness = 2; - Content.Anchor = Content.Origin = Anchor.Centre; - - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.TorusAlternate.With(size: 17), - Shear = new Vector2(-shear, 0) - } - }; - - if (width != null) - { - Width = width.Value; - } - else - { - AutoSizeAxes = Axes.X; - text.Margin = new MarginPadding { Horizontal = 15 }; - } } [BackgroundDependencyLoader] @@ -100,70 +47,22 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { - base.LoadComplete(); - + Active.BindDisabledChanged(disabled => Action = disabled ? (Action?)null : Active.Toggle, true); Active.BindValueChanged(_ => { - updateState(); + updateActiveState(); playSample(); }); - Active.BindDisabledChanged(disabled => - { - updateState(); - Action = disabled ? (Action?)null : Active.Toggle; - }, true); - FinishTransforms(true); + updateActiveState(); + base.LoadComplete(); } - protected override bool OnHover(HoverEvent e) + private void updateActiveState() { - updateState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - base.OnHoverLost(e); - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - Content.ScaleTo(0.8f, 2000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - Content.ScaleTo(1, 1000, Easing.OutElastic); - base.OnMouseUp(e); - } - - private void updateState() - { - var darkerColour = Active.Value ? colourProvider.Highlight1 : colourProvider.Background3; - var lighterColour = Active.Value ? colourProvider.Colour0 : colourProvider.Background1; - - if (Active.Disabled) - { - darkerColour = darkerColour.Darken(0.3f); - lighterColour = lighterColour.Darken(0.3f); - } - else if (IsHovered) - { - darkerColour = darkerColour.Lighten(0.3f); - lighterColour = lighterColour.Lighten(0.3f); - } - - background.FadeColour(darkerColour, 150, Easing.OutQuint); - Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(darkerColour, lighterColour), 150, Easing.OutQuint); - - var textColour = Active.Value ? colourProvider.Background6 : colourProvider.Content1; - if (Active.Disabled) - textColour = textColour.Opacity(0.6f); - - text.FadeColour(textColour, 150, Easing.OutQuint); + DarkerColour = Active.Value ? ColourProvider.Highlight1 : ColourProvider.Background3; + LighterColour = Active.Value ? ColourProvider.Colour0 : ColourProvider.Background1; + TextColour = Active.Value ? ColourProvider.Background6 : ColourProvider.Content1; } private void playSample() diff --git a/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs index c136ca6a19..60849cd6d4 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/JoystickSettings.cs @@ -20,6 +20,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input private SettingsSlider deadzoneSlider; + private Bindable handlerDeadzone; + + private Bindable localDeadzone; + public JoystickSettings(JoystickHandler joystickHandler) { this.joystickHandler = joystickHandler; @@ -28,6 +32,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input [BackgroundDependencyLoader] private void load() { + // use local bindable to avoid changing enabled state of game host's bindable. + handlerDeadzone = joystickHandler.DeadzoneThreshold.GetBoundCopy(); + localDeadzone = handlerDeadzone.GetUnboundCopy(); + Children = new Drawable[] { new SettingsCheckbox @@ -40,7 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = JoystickSettingsStrings.DeadzoneThreshold, KeyboardStep = 0.01f, DisplayAsPercentage = true, - Current = joystickHandler.DeadzoneThreshold, + Current = localDeadzone, }, }; } @@ -51,6 +59,17 @@ namespace osu.Game.Overlays.Settings.Sections.Input enabled.BindTo(joystickHandler.Enabled); enabled.BindValueChanged(e => deadzoneSlider.Current.Disabled = !e.NewValue, true); + + handlerDeadzone.BindValueChanged(val => + { + bool disabled = localDeadzone.Disabled; + + localDeadzone.Disabled = false; + localDeadzone.Value = val.NewValue; + localDeadzone.Disabled = disabled; + }, true); + + localDeadzone.BindValueChanged(val => handlerDeadzone.Value = val.NewValue); } } } diff --git a/osu.Game/Screens/Play/Break/BreakInfo.cs b/osu.Game/Screens/Play/Break/BreakInfo.cs index f95e949920..ead41a826a 100644 --- a/osu.Game/Screens/Play/Break/BreakInfo.cs +++ b/osu.Game/Screens/Play/Break/BreakInfo.cs @@ -43,8 +43,7 @@ namespace osu.Game.Screens.Play.Break Direction = FillDirection.Vertical, Children = new Drawable[] { - AccuracyDisplay = new PercentageBreakInfoLine(BeatmapsetsStrings.ShowStatsAccuracy), - + AccuracyDisplay = new PercentageBreakInfoLine(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy), // See https://github.com/ppy/osu/discussions/15185 // RankDisplay = new BreakInfoLine("Rank"), GradeDisplay = new BreakInfoLine("Grade"),