mirror of
https://github.com/ppy/osu.git
synced 2026-05-18 11:20:24 +08:00
9f91c2e25c
- Closes https://github.com/ppy/osu/issues/4287 - Probably closes https://github.com/ppy/osu/issues/25405 (but not retroactively) Up until now, whether or not a replay frame is emitted depended solely on the user's input, i.e. mouse movement or key presses/releases. This, intersected with the replay playback system which is given allowance to perform interpolation between replay frames, leads to potential situations wherein a replay can play inaccurately when a judgement takes place without user input meaningfully changing. One such case is slider ends with their 36ms of judgement leniency; see https://github.com/ppy/osu/issues/25405#issuecomment-2879031106 for details on that. To that end, this commit aims to counteract that issue by *forcing* an important replay frame to be emitted on every new judgement recorded during gameplay. This will only benefit rulesets wherein judgements can occur that are not inherently tied to user input changing, which are going to be osu! as mentioned above, and maybe possibly catch. I don't foresee this doing anything relevant for taiko or mania.
105 lines
2.9 KiB
C#
105 lines
2.9 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.
|
|
|
|
#nullable disable
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Input;
|
|
using osu.Framework.Input.Bindings;
|
|
using osu.Framework.Input.Events;
|
|
using osu.Game.Online.Spectator;
|
|
using osu.Game.Rulesets.Replays;
|
|
using osu.Game.Scoring;
|
|
using osuTK;
|
|
|
|
namespace osu.Game.Rulesets.UI
|
|
{
|
|
public abstract partial class ReplayRecorder<T> : ReplayRecorder, IKeyBindingHandler<T>
|
|
where T : struct
|
|
{
|
|
private readonly Score target;
|
|
|
|
private readonly List<T> pressedActions = new List<T>();
|
|
|
|
private InputManager inputManager;
|
|
|
|
/// <summary>
|
|
/// The frame rate to record replays at.
|
|
/// </summary>
|
|
public int RecordFrameRate { get; set; } = 60;
|
|
|
|
[Resolved]
|
|
private SpectatorClient spectatorClient { get; set; }
|
|
|
|
protected ReplayRecorder(Score target)
|
|
{
|
|
this.target = target;
|
|
|
|
RelativeSizeAxes = Axes.Both;
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
inputManager = GetContainingInputManager();
|
|
}
|
|
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
RecordFrame(false);
|
|
}
|
|
|
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
|
{
|
|
RecordFrame(false);
|
|
return base.OnMouseMove(e);
|
|
}
|
|
|
|
public bool OnPressed(KeyBindingPressEvent<T> e)
|
|
{
|
|
pressedActions.Add(e.Action);
|
|
RecordFrame(true);
|
|
return false;
|
|
}
|
|
|
|
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
|
{
|
|
pressedActions.Remove(e.Action);
|
|
RecordFrame(true);
|
|
}
|
|
|
|
public override void RecordFrame(bool important)
|
|
{
|
|
var last = target.Replay.Frames.LastOrDefault();
|
|
|
|
if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate) * Clock.Rate)
|
|
return;
|
|
|
|
var position = ScreenSpaceToGamefield?.Invoke(inputManager.CurrentState.Mouse.Position) ?? inputManager.CurrentState.Mouse.Position;
|
|
|
|
var frame = HandleFrame(position, pressedActions, last);
|
|
|
|
if (frame != null)
|
|
{
|
|
target.Replay.Frames.Add(frame);
|
|
|
|
spectatorClient?.HandleFrame(frame);
|
|
}
|
|
}
|
|
|
|
protected abstract ReplayFrame HandleFrame(Vector2 mousePosition, List<T> actions, ReplayFrame previousFrame);
|
|
}
|
|
|
|
public abstract partial class ReplayRecorder : Component
|
|
{
|
|
public Func<Vector2, Vector2> ScreenSpaceToGamefield;
|
|
|
|
public abstract void RecordFrame(bool important);
|
|
}
|
|
}
|