1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-16 20:32:55 +08:00
osu-lazer/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs
Bartłomiej Dach e5b51f769c
Fix incorrect assertion placement in spinner rotation tracker
Checking the delta after the application of rate is not correct. The
delta is in screen-space *before* the rate from rate-changing mods were
applied; the point of the application of the rate is to compensate for
the fact that the spinner is still judged in "track time" - but the goal
is to keep the spinner's difficulty *independent* of rate, which means
that with DT active the user's spin is "twice as effective" to
compensate for the fact that the spinner is twice as short in real time.

In another formulation, with DT active, the user gets to record replay
frames "half as often" as in normal gameplay.
2023-10-30 21:28:29 +01:00

133 lines
4.3 KiB
C#

// 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.
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Default
{
public partial class SpinnerRotationTracker : CircularContainer
{
public override bool IsPresent => true; // handle input when hidden
private readonly DrawableSpinner drawableSpinner;
private Vector2? mousePosition;
private float? lastAngle;
private float currentRotation;
private bool rotationTransferred;
[Resolved(canBeNull: true)]
private IGameplayClock? gameplayClock { get; set; }
public SpinnerRotationTracker(DrawableSpinner drawableSpinner)
{
this.drawableSpinner = drawableSpinner;
drawableSpinner.HitObjectApplied += resetState;
RelativeSizeAxes = Axes.Both;
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public bool Tracking { get; set; }
/// <summary>
/// Whether the spinning is spinning at a reasonable speed to be considered visually spinning.
/// </summary>
public readonly BindableBool IsSpinning = new BindableBool();
/// <summary>
/// Whether currently in the correct time range to allow spinning.
/// </summary>
private bool isSpinnableTime => drawableSpinner.HitObject.StartTime <= Time.Current && drawableSpinner.HitObject.EndTime > Time.Current;
protected override bool OnMouseMove(MouseMoveEvent e)
{
mousePosition = Parent!.ToLocalSpace(e.ScreenSpaceMousePosition);
return base.OnMouseMove(e);
}
protected override void Update()
{
base.Update();
if (mousePosition is Vector2 pos)
{
float thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(pos.X - DrawSize.X / 2, pos.Y - DrawSize.Y / 2));
float delta = lastAngle == null ? 0 : thisAngle - lastAngle.Value;
// Normalise the delta to -180 .. 180
if (delta > 180) delta -= 360;
if (delta < -180) delta += 360;
if (Tracking)
AddRotation(delta);
lastAngle = thisAngle;
}
IsSpinning.Value = isSpinnableTime && Math.Abs(currentRotation - Rotation) > 10f;
Rotation = (float)Interpolation.Damp(Rotation, currentRotation, 0.99, Math.Abs(Time.Elapsed));
}
/// <summary>
/// Rotate the disc by the provided angle (in addition to any existing rotation).
/// </summary>
/// <remarks>
/// Will be a no-op if not a valid time to spin.
/// </remarks>
/// <param name="delta">The delta angle.</param>
public void AddRotation(float delta)
{
if (!isSpinnableTime)
return;
if (!rotationTransferred)
{
currentRotation = Rotation;
rotationTransferred = true;
}
Debug.Assert(Math.Abs(delta) <= 180);
double rate = gameplayClock?.GetTrueGameplayRate() ?? Clock.Rate;
delta = (float)(delta * Math.Abs(rate));
currentRotation += delta;
drawableSpinner.Result.History.ReportDelta(Time.Current, delta);
}
private void resetState(DrawableHitObject obj)
{
Tracking = false;
IsSpinning.Value = false;
mousePosition = null;
lastAngle = null;
currentRotation = 0;
Rotation = 0;
rotationTransferred = false;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (drawableSpinner.IsNotNull())
drawableSpinner.HitObjectApplied -= resetState;
}
}
}