diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 9f9c2a09b6..2e07117734 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -2,14 +2,81 @@ // 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.Drawables; +using osu.Game.Rulesets.Objects.Types; +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, IUpdatableByHitObject, IUpdatableByPlayfield { 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; + + private bool hitStill; + private bool hitOnce; + + public void Update(DrawableHitObject o) + { + const float relax_leniency = 3; + + if (!(o is DrawableOsuHitObject d)) + return; + + double t = d.Clock.CurrentTime; + + if (t >= d.HitObject.StartTime - relax_leniency) + { + if (d.HitObject is IHasEndTime e && t > e.EndTime || d.IsHit) + return; + + hitStill |= d is DrawableSlider s && (s.Ball.IsHovered || d.IsHovered) || d is DrawableSpinner; + + hitOnce |= d is DrawableHitCircle && d.IsHovered; + } + } + + public void Update(Playfield r) + { + var d = r.HitObjects.Objects.First(h => h is DrawableOsuHitObject) as DrawableOsuHitObject; + if (hitOnce) + { + hit(d, false); + hit(d, true); + } + hit(d, hitStill); + + hitOnce = false; + hitStill = false; + } + + private bool wasHit; + private bool wasLeft; + + private void hit(DrawableOsuHitObject d, bool hitting) + { + if (wasHit == hitting) + return; + wasHit = hitting; + + var l = new ReplayState + { + PressedActions = new List() + }; + if (hitting) + { + l.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton); + wasLeft = !wasLeft; + } + d.OsuActionInputManager.HandleCustomInput(new InputState(), l); + } } } diff --git a/osu.Game/Rulesets/Mods/IUpdatableByHitObject.cs b/osu.Game/Rulesets/Mods/IUpdatableByHitObject.cs new file mode 100644 index 0000000000..657145b911 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IUpdatableByHitObject.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.Objects.Drawables; + +namespace osu.Game.Rulesets.Mods +{ + public interface IUpdatableByHitObject : IApplicableMod + { + void Update(DrawableHitObject o); + } +} diff --git a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs new file mode 100644 index 0000000000..39781a875d --- /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 r); + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index a22aaa784f..31dd5a4a91 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -13,6 +13,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using OpenTK.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Objects.Drawables { @@ -41,6 +43,8 @@ public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColo public IReadOnlyList Judgements => judgements; private readonly List judgements = new List(); + private WorkingBeatmap beatmap; + /// /// Whether a visible judgement should be displayed when this representation is hit. /// @@ -80,8 +84,9 @@ protected DrawableHitObject(HitObject hitObject) } [BackgroundDependencyLoader] - private void load() + private void load(IBindableBeatmap b) { + beatmap = b.Value; var samples = GetSamples().ToArray(); if (samples.Any()) @@ -132,6 +137,11 @@ protected override void Update() { base.Update(); + if(beatmap != null) + foreach (var m in beatmap.Mods.Value) + if (m is IUpdatableByHitObject u) + u.Update(this); + var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; while (judgements.Count > 0) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 2f44d99e18..7116dc53a9 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -6,6 +6,8 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Configuration; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.UI { @@ -42,9 +44,12 @@ protected Playfield(float? customWidth = null, float? customHeight = null) RelativeSizeAxes = Axes.Both; } + private WorkingBeatmap beatmap; + [BackgroundDependencyLoader] - private void load() + private void load(IBindableBeatmap b) { + beatmap = b.Value; HitObjects = CreateHitObjectContainer(); HitObjects.RelativeSizeAxes = Axes.Both; @@ -87,5 +92,15 @@ protected void AddNested(Playfield otherPlayfield) /// 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 m in beatmap.Mods.Value) + if (m is IUpdatableByPlayfield u) + u.Update(this); + } } }