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;
|
|
|
|
|
using System.Collections.Generic;
|
2018-07-21 10:38:28 +08:00
|
|
|
|
using osu.Framework.Input.StateChanges;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using osu.Game.Input.Handlers;
|
2018-11-28 16:20:37 +08:00
|
|
|
|
using osu.Game.Replays;
|
2018-11-20 15:51:59 +08:00
|
|
|
|
using osuTK;
|
|
|
|
|
using osuTK.Input;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Replays
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The ReplayHandler will take a replay and handle the propagation of updates to the input stack.
|
|
|
|
|
/// It handles logic of any frames which *must* be executed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public abstract class FramedReplayInputHandler<TFrame> : ReplayInputHandler
|
|
|
|
|
where TFrame : ReplayFrame
|
|
|
|
|
{
|
|
|
|
|
private readonly Replay replay;
|
|
|
|
|
|
|
|
|
|
protected List<ReplayFrame> Frames => replay.Frames;
|
|
|
|
|
|
|
|
|
|
public TFrame CurrentFrame => !HasFrames ? null : (TFrame)Frames[currentFrameIndex];
|
|
|
|
|
public TFrame NextFrame => !HasFrames ? null : (TFrame)Frames[nextFrameIndex];
|
|
|
|
|
|
|
|
|
|
private int currentFrameIndex;
|
|
|
|
|
|
|
|
|
|
private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1);
|
|
|
|
|
|
|
|
|
|
protected FramedReplayInputHandler(Replay replay)
|
|
|
|
|
{
|
|
|
|
|
this.replay = replay;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool advanceFrame()
|
|
|
|
|
{
|
|
|
|
|
int newFrame = nextFrameIndex;
|
|
|
|
|
|
|
|
|
|
//ensure we aren't at an extent.
|
|
|
|
|
if (newFrame == currentFrameIndex) return false;
|
|
|
|
|
|
|
|
|
|
currentFrameIndex = newFrame;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-11 22:00:26 +08:00
|
|
|
|
public override List<IInput> GetPendingInputs() => new List<IInput>();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
public bool AtLastFrame => currentFrameIndex == Frames.Count - 1;
|
|
|
|
|
public bool AtFirstFrame => currentFrameIndex == 0;
|
|
|
|
|
|
|
|
|
|
private const double sixty_frame_time = 1000.0 / 60;
|
|
|
|
|
|
|
|
|
|
protected double CurrentTime { get; private set; }
|
|
|
|
|
private int currentDirection;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data.
|
|
|
|
|
/// Disabling this can make replay playback smoother (useful for autoplay, currently).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool FrameAccuratePlayback = true;
|
|
|
|
|
|
|
|
|
|
protected bool HasFrames => Frames.Count > 0;
|
|
|
|
|
|
|
|
|
|
private bool inImportantSection =>
|
|
|
|
|
HasFrames && FrameAccuratePlayback &&
|
|
|
|
|
//a button is in a pressed state
|
|
|
|
|
IsImportant(currentDirection > 0 ? CurrentFrame : NextFrame) &&
|
|
|
|
|
//the next frame is within an allowable time span
|
|
|
|
|
Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2;
|
|
|
|
|
|
|
|
|
|
protected virtual bool IsImportant(TFrame frame) => false;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Update the current frame based on an incoming time value.
|
|
|
|
|
/// There are cases where we return a "must-use" time value that is different from the input.
|
|
|
|
|
/// This is to ensure accurate playback of replay data.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="time">The time which we should use for finding the current frame.</param>
|
|
|
|
|
/// <returns>The usable time value. If null, we should not advance time as we do not have enough data.</returns>
|
|
|
|
|
public override double? SetFrameFromTime(double time)
|
|
|
|
|
{
|
|
|
|
|
currentDirection = time.CompareTo(CurrentTime);
|
|
|
|
|
if (currentDirection == 0) currentDirection = 1;
|
|
|
|
|
|
|
|
|
|
if (HasFrames)
|
|
|
|
|
{
|
|
|
|
|
// check if the next frame is in the "future" for the current playback direction
|
|
|
|
|
if (currentDirection != time.CompareTo(NextFrame.Time))
|
|
|
|
|
{
|
|
|
|
|
// if we didn't change frames, we need to ensure we are allowed to run frames in between, else return null.
|
|
|
|
|
if (inImportantSection)
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
else if (advanceFrame())
|
|
|
|
|
{
|
|
|
|
|
// If going backwards, we need to execute once _before_ the frame time to reverse any judgements
|
|
|
|
|
// that would occur as a result of this frame in forward playback
|
|
|
|
|
if (currentDirection == -1)
|
|
|
|
|
return CurrentTime = CurrentFrame.Time - 1;
|
|
|
|
|
return CurrentTime = CurrentFrame.Time;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return CurrentTime = time;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-21 10:38:28 +08:00
|
|
|
|
protected class ReplayMouseState : osu.Framework.Input.States.MouseState
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
public ReplayMouseState(Vector2 position)
|
|
|
|
|
{
|
|
|
|
|
Position = position;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-21 10:38:28 +08:00
|
|
|
|
protected class ReplayKeyboardState : osu.Framework.Input.States.KeyboardState
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
public ReplayKeyboardState(List<Key> keys)
|
|
|
|
|
{
|
2018-06-11 22:00:26 +08:00
|
|
|
|
foreach (var key in keys)
|
|
|
|
|
Keys.Add(key);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|