diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 9f9c2a09b6..4047e057cb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -2,14 +2,89 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; +using osu.Framework.Input.States; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.UI; +using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModRelax : ModRelax + public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToRulesetContainer { 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; + bool requiresHit = false; + + const float relax_leniency = 3; + + foreach (var drawable in playfield.HitObjects.AliveObjects) + { + if (!(drawable is DrawableOsuHitObject osuHit)) + continue; + + double time = osuHit.Clock.CurrentTime; + double relativetime = time - osuHit.HitObject.StartTime; + + if (time < osuHit.HitObject.StartTime - relax_leniency) continue; + + if (osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime || osuHit.IsHit) + continue; + + requiresHit |= osuHit is DrawableHitCircle && osuHit.IsHovered && osuHit.HitObject.HitWindows.CanBeHit(relativetime); + requiresHold |= osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered) || osuHit is DrawableSpinner; + } + + if (requiresHit) + { + addAction(false); + addAction(true); + } + + addAction(requiresHold); + } + + private bool wasHit; + private bool wasLeft; + + private OsuInputManager osuInputManager; + + private void addAction(bool hitting) + { + if (wasHit == hitting) + return; + + wasHit = hitting; + + var state = new ReplayState + { + PressedActions = new List() + }; + + if (hitting) + { + state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); + wasLeft = !wasLeft; + } + + osuInputManager.HandleCustomInput(new InputState(), state); + } + + public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + // grab the input manager for future use. + osuInputManager = (OsuInputManager)rulesetContainer.KeyBindingInputManager; + osuInputManager.AllowUserPresses = false; + } } } diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index d9ae836e0a..e7bbe755a0 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.ComponentModel; using osu.Framework.Input.Bindings; +using osu.Framework.Input.EventArgs; +using osu.Framework.Input.States; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu @@ -12,8 +14,35 @@ namespace osu.Game.Rulesets.Osu { public IEnumerable PressedActions => KeyBindingContainer.PressedActions; - public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique) + public bool AllowUserPresses { + set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value; + } + + protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + => new OsuKeyBindingContainer(ruleset, variant, unique); + + public OsuInputManager(RulesetInfo ruleset) + : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + } + + private class OsuKeyBindingContainer : RulesetKeyBindingContainer + { + public bool AllowUserPresses = true; + + public OsuKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => AllowUserPresses && base.OnKeyDown(state, args); + protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => AllowUserPresses && base.OnKeyUp(state, args); + protected override bool OnJoystickPress(InputState state, JoystickEventArgs args) => AllowUserPresses && base.OnJoystickPress(state, args); + protected override bool OnJoystickRelease(InputState state, JoystickEventArgs args) => AllowUserPresses && base.OnJoystickRelease(state, args); + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => AllowUserPresses && base.OnMouseDown(state, args); + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => AllowUserPresses && base.OnMouseUp(state, args); + protected override bool OnScroll(InputState state) => AllowUserPresses && base.OnScroll(state); } } @@ -21,6 +50,7 @@ namespace osu.Game.Rulesets.Osu { [Description("Left Button")] LeftButton, + [Description("Right Button")] RightButton } diff --git a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs new file mode 100644 index 0000000000..be879759bd --- /dev/null +++ b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mods +{ + public interface IUpdatableByPlayfield : IApplicableMod + { + void Update(Playfield playfield); + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index dab8f7304e..da14fb54d6 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -9,6 +9,8 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Configuration; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.UI { @@ -51,9 +53,13 @@ namespace osu.Game.Rulesets.UI RelativeSizeAxes = Axes.Both; } + private WorkingBeatmap beatmap; + [BackgroundDependencyLoader] - private void load() + private void load(IBindableBeatmap beatmap) { + this.beatmap = beatmap.Value; + HitObjects = CreateHitObjectContainer(); HitObjects.RelativeSizeAxes = Axes.Both; @@ -92,5 +98,15 @@ namespace osu.Game.Rulesets.UI /// Creates the container that will be used to contain the s. /// protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); + + protected override void Update() + { + base.Update(); + + if (beatmap != null) + foreach (var mod in beatmap.Mods.Value) + if (mod is IUpdatableByPlayfield updatable) + updatable.Update(this); + } } }