mirror of
https://github.com/ppy/osu.git
synced 2025-01-07 21:32:57 +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
|
#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;
|
private readonly HitWindows defaultHitWindows;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -54,9 +49,6 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
public OsuAutoGenerator(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public OsuAutoGenerator(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
: base(beatmap, mods)
|
: base(beatmap, mods)
|
||||||
{
|
{
|
||||||
// Already superhuman, but still somewhat realistic
|
|
||||||
reactionTime = ApplyModsToRate(100);
|
|
||||||
|
|
||||||
defaultHitWindows = new OsuHitWindows();
|
defaultHitWindows = new OsuHitWindows();
|
||||||
defaultHitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
|
defaultHitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
|
||||||
}
|
}
|
||||||
@ -242,7 +234,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[^1];
|
OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[^1];
|
||||||
|
|
||||||
// Wait until Auto could "see and react" to the next note.
|
// 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)
|
if (waitTime > lastFrame.Time)
|
||||||
{
|
{
|
||||||
@ -252,7 +244,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
|
|
||||||
Vector2 lastPosition = lastFrame.Position;
|
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.
|
// 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
|
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.
|
timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
|
||||||
{
|
{
|
||||||
// Perform eased movement
|
// 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);
|
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 });
|
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
|
// Add frames to click the hitobject
|
||||||
private void addHitObjectClickFrames(OsuHitObject h, Vector2 startPosition, float spinnerDirection)
|
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);
|
float angle = radius == 0 ? 0 : MathF.Atan2(difference.Y, difference.X);
|
||||||
|
|
||||||
double t;
|
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);
|
Vector2 pos = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS);
|
||||||
AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action));
|
AddFrameToReplay(new OsuReplayFrame((int)nextFrame, new Vector2(pos.X, pos.Y), action));
|
||||||
|
|
||||||
|
previousFrame = nextFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
t = ApplyModsToTime(spinner.EndTime - h.StartTime) * spinnerDirection;
|
t = ApplyModsToTimeDelta(previousFrame, spinner.EndTime) * spinnerDirection;
|
||||||
Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);
|
angle += (float)t / 20;
|
||||||
|
|
||||||
|
Vector2 endPosition = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS);
|
||||||
|
|
||||||
AddFrameToReplay(new OsuReplayFrame(spinner.EndTime, new Vector2(endPosition.X, endPosition.Y), action));
|
AddFrameToReplay(new OsuReplayFrame(spinner.EndTime, new Vector2(endPosition.X, endPosition.Y), action));
|
||||||
|
|
||||||
@ -361,7 +367,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Slider slider:
|
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);
|
Vector2 pos = slider.StackedPositionAt(j / slider.Duration);
|
||||||
AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action));
|
AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action));
|
||||||
|
@ -5,6 +5,7 @@ using osuTK;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
@ -23,33 +24,61 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
|
|
||||||
public const float SPIN_RADIUS = 50;
|
public const float SPIN_RADIUS = 50;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The time in ms between each ReplayFrame.
|
|
||||||
/// </summary>
|
|
||||||
protected readonly double FrameDelay;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Construction / Initialisation
|
#region Construction / Initialisation
|
||||||
|
|
||||||
protected Replay Replay;
|
protected Replay Replay;
|
||||||
protected List<ReplayFrame> Frames => Replay.Frames;
|
protected List<ReplayFrame> Frames => Replay.Frames;
|
||||||
|
private readonly IReadOnlyList<IApplicableToRate> timeAffectingMods;
|
||||||
|
|
||||||
protected OsuAutoGeneratorBase(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
protected OsuAutoGeneratorBase(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
: base(beatmap)
|
: base(beatmap)
|
||||||
{
|
{
|
||||||
Replay = new Replay();
|
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.
|
timeAffectingMods = mods.OfType<IApplicableToRate>().ToList();
|
||||||
FrameDelay = ApplyModsToRate(1000.0 / 60.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Utilities
|
#region Utilities
|
||||||
|
|
||||||
protected double ApplyModsToTime(double v) => v;
|
/// <summary>
|
||||||
protected double ApplyModsToRate(double v) => v;
|
/// 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>
|
private class ReplayFrameComparer : IComparer<ReplayFrame>
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user