mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 20:03:22 +08:00
Use IApplicableToRate in osu! auto generator
This commit is contained in:
parent
3fabe247b0
commit
0e1ec703d3
@ -35,11 +35,6 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// The "reaction time" in ms between "seeing" a new hit object and moving to "react" to it.
|
||||
/// </summary>
|
||||
private readonly double reactionTime;
|
||||
|
||||
private readonly HitWindows defaultHitWindows;
|
||||
|
||||
/// <summary>
|
||||
@ -54,9 +49,6 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
public OsuAutoGenerator(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||
: base(beatmap, mods)
|
||||
{
|
||||
// Already superhuman, but still somewhat realistic
|
||||
reactionTime = ApplyModsToRate(100);
|
||||
|
||||
defaultHitWindows = new OsuHitWindows();
|
||||
defaultHitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
|
||||
}
|
||||
@ -242,7 +234,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[^1];
|
||||
|
||||
// Wait until Auto could "see and react" to the next note.
|
||||
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime);
|
||||
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - getReactionTime(h.StartTime - h.TimePreempt));
|
||||
|
||||
if (waitTime > lastFrame.Time)
|
||||
{
|
||||
@ -252,7 +244,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
Vector2 lastPosition = lastFrame.Position;
|
||||
|
||||
double timeDifference = ApplyModsToTime(h.StartTime - lastFrame.Time);
|
||||
double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime);
|
||||
|
||||
// Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up.
|
||||
if (timeDifference > 0 && // Sanity checks
|
||||
@ -260,7 +252,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
|
||||
{
|
||||
// Perform eased movement
|
||||
for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay)
|
||||
for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time))
|
||||
{
|
||||
Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing);
|
||||
AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions });
|
||||
@ -274,6 +266,14 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the "reaction time" in ms between "seeing" a new hit object and moving to "react" to it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Already superhuman, but still somewhat realistic.
|
||||
/// </remarks>
|
||||
private double getReactionTime(double timeInstant) => ApplyModsToRate(timeInstant, 100);
|
||||
|
||||
// Add frames to click the hitobject
|
||||
private void addHitObjectClickFrames(OsuHitObject h, Vector2 startPosition, float spinnerDirection)
|
||||
{
|
||||
@ -343,17 +343,23 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
float angle = radius == 0 ? 0 : MathF.Atan2(difference.Y, difference.X);
|
||||
|
||||
double t;
|
||||
double previousFrame = h.StartTime;
|
||||
|
||||
for (double j = h.StartTime + FrameDelay; j < spinner.EndTime; j += FrameDelay)
|
||||
for (double nextFrame = h.StartTime + GetFrameDelay(h.StartTime); nextFrame < spinner.EndTime; nextFrame += GetFrameDelay(nextFrame))
|
||||
{
|
||||
t = ApplyModsToTime(j - h.StartTime) * spinnerDirection;
|
||||
t = ApplyModsToTimeDelta(previousFrame, nextFrame) * spinnerDirection;
|
||||
angle += (float)t / 20;
|
||||
|
||||
Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);
|
||||
AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action));
|
||||
Vector2 pos = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS);
|
||||
AddFrameToReplay(new OsuReplayFrame((int)nextFrame, new Vector2(pos.X, pos.Y), action));
|
||||
|
||||
previousFrame = nextFrame;
|
||||
}
|
||||
|
||||
t = ApplyModsToTime(spinner.EndTime - h.StartTime) * spinnerDirection;
|
||||
Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);
|
||||
t = ApplyModsToTimeDelta(previousFrame, spinner.EndTime) * spinnerDirection;
|
||||
angle += (float)t / 20;
|
||||
|
||||
Vector2 endPosition = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS);
|
||||
|
||||
AddFrameToReplay(new OsuReplayFrame(spinner.EndTime, new Vector2(endPosition.X, endPosition.Y), action));
|
||||
|
||||
@ -361,7 +367,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
break;
|
||||
|
||||
case Slider slider:
|
||||
for (double j = FrameDelay; j < slider.Duration; j += FrameDelay)
|
||||
for (double j = GetFrameDelay(slider.StartTime); j < slider.Duration; j += GetFrameDelay(slider.StartTime + j))
|
||||
{
|
||||
Vector2 pos = slider.StackedPositionAt(j / slider.Duration);
|
||||
AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action));
|
||||
|
@ -5,6 +5,7 @@ using osuTK;
|
||||
using osu.Game.Beatmaps;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
@ -23,33 +24,61 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
public const float SPIN_RADIUS = 50;
|
||||
|
||||
/// <summary>
|
||||
/// The time in ms between each ReplayFrame.
|
||||
/// </summary>
|
||||
protected readonly double FrameDelay;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction / Initialisation
|
||||
|
||||
protected Replay Replay;
|
||||
protected List<ReplayFrame> Frames => Replay.Frames;
|
||||
private readonly IReadOnlyList<IApplicableToRate> timeAffectingMods;
|
||||
|
||||
protected OsuAutoGeneratorBase(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||
: base(beatmap)
|
||||
{
|
||||
Replay = new Replay();
|
||||
|
||||
// We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps.
|
||||
FrameDelay = ApplyModsToRate(1000.0 / 60.0);
|
||||
timeAffectingMods = mods.OfType<IApplicableToRate>().ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utilities
|
||||
|
||||
protected double ApplyModsToTime(double v) => v;
|
||||
protected double ApplyModsToRate(double v) => v;
|
||||
/// <summary>
|
||||
/// Returns the real duration of time between <paramref name="startTime"/> and <paramref name="endTime"/>
|
||||
/// after applying rate-affecting mods.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method should only be used when <paramref name="startTime"/> and <paramref name="endTime"/> are very close.
|
||||
/// That is because the track rate might be changing with time,
|
||||
/// and the method used here is a rough instantaneous approximation.
|
||||
/// </remarks>
|
||||
/// <param name="startTime">The start time of the time delta, in original track time.</param>
|
||||
/// <param name="endTime">The end time of the time delta, in original track time.</param>
|
||||
protected double ApplyModsToTimeDelta(double startTime, double endTime)
|
||||
{
|
||||
double delta = endTime - startTime;
|
||||
|
||||
foreach (var mod in timeAffectingMods)
|
||||
delta /= mod.ApplyToRate(startTime);
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
protected double ApplyModsToRate(double time, double rate)
|
||||
{
|
||||
foreach (var mod in timeAffectingMods)
|
||||
rate = mod.ApplyToRate(time, rate);
|
||||
return rate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the interval after which the next <see cref="ReplayFrame"/> should be generated,
|
||||
/// in milliseconds.
|
||||
/// </summary>
|
||||
/// <param name="time">The time of the previous frame.</param>
|
||||
protected double GetFrameDelay(double time)
|
||||
=> ApplyModsToRate(time, 1000.0 / 60);
|
||||
|
||||
private class ReplayFrameComparer : IComparer<ReplayFrame>
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user