2019-01-24 16:43:03 +08:00
|
|
|
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
|
|
|
// See the LICENCE file in the repository root for full licence text.
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
using System;
|
2018-08-03 20:03:11 +08:00
|
|
|
|
using System.Collections.Generic;
|
2019-09-02 17:31:33 +08:00
|
|
|
|
using System.Diagnostics;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using System.Linq;
|
2022-08-11 04:09:11 +08:00
|
|
|
|
using osu.Framework.Localisation;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using osu.Game.Rulesets.Mods;
|
2018-08-03 20:03:11 +08:00
|
|
|
|
using osu.Game.Rulesets.Objects.Types;
|
2018-08-16 17:18:15 +08:00
|
|
|
|
using osu.Game.Rulesets.Osu.Objects;
|
2018-08-03 20:03:11 +08:00
|
|
|
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
2020-02-14 16:13:50 +08:00
|
|
|
|
using osu.Game.Rulesets.Replays;
|
2018-08-03 20:03:11 +08:00
|
|
|
|
using osu.Game.Rulesets.UI;
|
2020-04-21 14:28:25 +08:00
|
|
|
|
using osu.Game.Screens.Play;
|
2018-08-03 20:03:11 +08:00
|
|
|
|
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Osu.Mods
|
|
|
|
|
{
|
2020-04-21 14:28:25 +08:00
|
|
|
|
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2022-08-11 04:09:11 +08:00
|
|
|
|
public override LocalisableString Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
2022-07-13 06:07:41 +08:00
|
|
|
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
2018-08-03 20:03:11 +08:00
|
|
|
|
|
2020-02-14 15:58:56 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// How early before a hitobject's start time to trigger a hit.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private const float relax_leniency = 3;
|
|
|
|
|
|
2020-02-14 16:13:50 +08:00
|
|
|
|
private bool isDownState;
|
2020-02-14 16:03:23 +08:00
|
|
|
|
private bool wasLeft;
|
|
|
|
|
|
2022-07-31 21:43:16 +08:00
|
|
|
|
private OsuInputManager osuInputManager = null!;
|
2020-02-14 16:03:23 +08:00
|
|
|
|
|
2022-07-31 21:43:16 +08:00
|
|
|
|
private ReplayState<OsuAction> state = null!;
|
2020-02-14 16:13:50 +08:00
|
|
|
|
private double lastStateChangeTime;
|
|
|
|
|
|
2020-04-21 14:28:25 +08:00
|
|
|
|
private bool hasReplay;
|
|
|
|
|
|
2020-02-14 16:03:23 +08:00
|
|
|
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
|
|
|
|
{
|
|
|
|
|
// grab the input manager for future use.
|
|
|
|
|
osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
|
2020-04-21 14:28:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ApplyToPlayer(Player player)
|
|
|
|
|
{
|
|
|
|
|
if (osuInputManager.ReplayInputHandler != null)
|
|
|
|
|
{
|
|
|
|
|
hasReplay = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-04-21 15:06:40 +08:00
|
|
|
|
|
|
|
|
|
osuInputManager.AllowUserPresses = false;
|
2020-02-14 16:03:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-05 15:52:19 +08:00
|
|
|
|
public void Update(Playfield playfield)
|
2018-08-03 20:03:11 +08:00
|
|
|
|
{
|
2020-04-21 14:28:25 +08:00
|
|
|
|
if (hasReplay)
|
|
|
|
|
return;
|
|
|
|
|
|
2018-08-16 17:18:15 +08:00
|
|
|
|
bool requiresHold = false;
|
|
|
|
|
bool requiresHit = false;
|
2018-08-05 15:52:19 +08:00
|
|
|
|
|
2020-02-14 15:58:56 +08:00
|
|
|
|
double time = playfield.Clock.CurrentTime;
|
2018-08-03 20:03:11 +08:00
|
|
|
|
|
2020-02-14 15:58:56 +08:00
|
|
|
|
foreach (var h in playfield.HitObjectContainer.AliveObjects.OfType<DrawableOsuHitObject>())
|
2018-08-05 15:52:19 +08:00
|
|
|
|
{
|
2020-02-14 15:58:56 +08:00
|
|
|
|
// we are not yet close enough to the object.
|
|
|
|
|
if (time < h.HitObject.StartTime - relax_leniency)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// already hit or beyond the hittable end time.
|
2020-05-27 11:38:39 +08:00
|
|
|
|
if (h.IsHit || (h.HitObject is IHasDuration hasEnd && time > hasEnd.EndTime))
|
2018-08-05 15:52:19 +08:00
|
|
|
|
continue;
|
2018-08-03 20:03:11 +08:00
|
|
|
|
|
2020-02-14 15:58:56 +08:00
|
|
|
|
switch (h)
|
|
|
|
|
{
|
2020-02-14 16:00:55 +08:00
|
|
|
|
case DrawableHitCircle circle:
|
|
|
|
|
handleHitCircle(circle);
|
2020-02-14 15:58:56 +08:00
|
|
|
|
break;
|
2018-08-03 20:03:11 +08:00
|
|
|
|
|
2020-02-14 15:58:56 +08:00
|
|
|
|
case DrawableSlider slider:
|
2020-02-14 16:00:55 +08:00
|
|
|
|
// Handles cases like "2B" beatmaps, where sliders may be overlapping and simply holding is not enough.
|
|
|
|
|
if (!slider.HeadCircle.IsHit)
|
|
|
|
|
handleHitCircle(slider.HeadCircle);
|
|
|
|
|
|
2020-02-14 15:58:56 +08:00
|
|
|
|
requiresHold |= slider.Ball.IsHovered || h.IsHovered;
|
|
|
|
|
break;
|
2018-08-03 20:03:11 +08:00
|
|
|
|
|
2022-02-12 08:51:09 +08:00
|
|
|
|
case DrawableSpinner spinner:
|
2022-02-12 11:15:03 +08:00
|
|
|
|
requiresHold |= spinner.HitObject.SpinsRequired > 0;
|
2020-02-14 15:58:56 +08:00
|
|
|
|
break;
|
2019-09-02 17:31:33 +08:00
|
|
|
|
}
|
2018-08-03 20:03:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-16 17:18:15 +08:00
|
|
|
|
if (requiresHit)
|
2018-08-03 20:03:11 +08:00
|
|
|
|
{
|
2020-02-14 16:13:50 +08:00
|
|
|
|
changeState(false);
|
|
|
|
|
changeState(true);
|
2018-08-03 20:03:11 +08:00
|
|
|
|
}
|
2018-08-16 17:18:15 +08:00
|
|
|
|
|
2020-02-14 16:13:50 +08:00
|
|
|
|
if (requiresHold)
|
|
|
|
|
changeState(true);
|
|
|
|
|
else if (isDownState && time - lastStateChangeTime > AutoGenerator.KEY_UP_DELAY)
|
|
|
|
|
changeState(false);
|
2020-02-14 16:00:55 +08:00
|
|
|
|
|
|
|
|
|
void handleHitCircle(DrawableHitCircle circle)
|
|
|
|
|
{
|
2020-03-31 14:17:27 +08:00
|
|
|
|
if (!circle.HitArea.IsHovered)
|
2020-02-14 16:00:55 +08:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Debug.Assert(circle.HitObject.HitWindows != null);
|
|
|
|
|
requiresHit |= circle.HitObject.HitWindows.CanBeHit(time - circle.HitObject.StartTime);
|
|
|
|
|
}
|
2018-08-03 20:03:11 +08:00
|
|
|
|
|
2020-02-14 16:13:50 +08:00
|
|
|
|
void changeState(bool down)
|
|
|
|
|
{
|
|
|
|
|
if (isDownState == down)
|
|
|
|
|
return;
|
2018-08-16 17:18:15 +08:00
|
|
|
|
|
2020-02-14 16:13:50 +08:00
|
|
|
|
isDownState = down;
|
|
|
|
|
lastStateChangeTime = time;
|
2018-08-03 20:03:11 +08:00
|
|
|
|
|
2020-02-14 16:13:50 +08:00
|
|
|
|
state = new ReplayState<OsuAction>
|
|
|
|
|
{
|
|
|
|
|
PressedActions = new List<OsuAction>()
|
|
|
|
|
};
|
2018-08-16 17:18:15 +08:00
|
|
|
|
|
2020-02-14 16:13:50 +08:00
|
|
|
|
if (down)
|
|
|
|
|
{
|
|
|
|
|
state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton);
|
|
|
|
|
wasLeft = !wasLeft;
|
|
|
|
|
}
|
2018-08-16 17:18:15 +08:00
|
|
|
|
|
2022-07-31 21:43:16 +08:00
|
|
|
|
state.Apply(osuInputManager.CurrentState, osuInputManager);
|
2020-02-14 16:13:50 +08:00
|
|
|
|
}
|
2018-08-16 17:18:15 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
}
|