1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 02:07:34 +08:00

Create "AutoGenerator" base class and interface.

This commit is contained in:
Thomas Tan 2017-04-29 02:08:48 +08:00
parent 2af6c7aa00
commit 9b8b88601f
10 changed files with 125 additions and 89 deletions

View File

@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Mods
protected override Score CreateReplayScore(Beatmap<OsuHitObject> beatmap) => new Score protected override Score CreateReplayScore(Beatmap<OsuHitObject> beatmap) => new Score
{ {
Replay = new OsuAutoReplay(beatmap) Replay = new OsuAutoGenerator(beatmap).Generate()
}; };
} }

View File

@ -7,80 +7,82 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Users;
namespace osu.Game.Rulesets.Osu.Replays namespace osu.Game.Rulesets.Osu.Replays
{ {
public class OsuAutoReplay : OsuAutoReplayBase public class OsuAutoGenerator : OsuAutoGeneratorBase
{ {
// Options #region Parameters
/// <summary> /// <summary>
/// If delayed movements should be used, causing the cursor to stay on each hitobject for as long as possible. /// If delayed movements should be used, causing the cursor to stay on each hitobject for as long as possible.
/// Mainly for Autopilot. /// Mainly for Autopilot.
/// </summary> /// </summary>
public readonly bool DelayedMovements; // ModManager.CheckActive(Mods.Relax2); public bool DelayedMovements; // ModManager.CheckActive(Mods.Relax2);
#endregion
// Local constants #region Constants
/// <summary> /// <summary>
/// The "reaction time" in ms between "seeing" a new hit object and moving to "react" to it. /// The "reaction time" in ms between "seeing" a new hit object and moving to "react" to it.
/// </summary> /// </summary>
private double reactionTime; private readonly double reactionTime;
/// <summary> /// <summary>
/// What easing to use when moving between hitobjects /// What easing to use when moving between hitobjects
/// </summary> /// </summary>
private EasingTypes preferredEasing => DelayedMovements ? EasingTypes.InOutCubic : EasingTypes.Out; private EasingTypes preferredEasing => DelayedMovements ? EasingTypes.InOutCubic : EasingTypes.Out;
#endregion
#region Construction / Initialisation
public OsuAutoGenerator(Beatmap<OsuHitObject> beatmap)
: base(beatmap)
{
// Already superhuman, but still somewhat realistic
reactionTime = applyModsToRate(100);
}
#endregion
#region Generator
/// <summary> /// <summary>
/// Which button (left or right) to use for the current hitobject. /// Which button (left or right) to use for the current hitobject.
/// Even means LMB will be used to click, odd means RMB will be used. /// Even means LMB will be used to click, odd means RMB will be used.
/// This keeps track of the button previously used for alt/singletap logic. /// This keeps track of the button previously used for alt/singletap logic.
/// </summary> /// </summary>
private int buttonIndex; private int buttonIndex;
public OsuAutoReplay(Beatmap<OsuHitObject> beatmap) public override Replay Generate()
: base(beatmap)
{
}
protected override void Initialise()
{
base.Initialise();
// Already superhuman, but still somewhat realistic
reactionTime = applyModsToRate(100);
}
protected override void CreateAutoReplay()
{ {
buttonIndex = 0; buttonIndex = 0;
addFrameToReplay(new ReplayFrame(-100000, 256, 500, ReplayButtonState.None)); addFrameToReplay(new ReplayFrame(-100000, 256, 500, ReplayButtonState.None));
addFrameToReplay(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1500, 256, 500, ReplayButtonState.None)); addFrameToReplay(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, 256, 500, ReplayButtonState.None));
addFrameToReplay(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1000, 256, 192, ReplayButtonState.None)); addFrameToReplay(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, 256, 192, ReplayButtonState.None));
for (int i = 0; i < beatmap.HitObjects.Count; i++) for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{ {
OsuHitObject h = beatmap.HitObjects[i]; OsuHitObject h = Beatmap.HitObjects[i];
if (DelayedMovements && i > 0) if (DelayedMovements && i > 0)
{ {
OsuHitObject prev = beatmap.HitObjects[i - 1]; OsuHitObject prev = Beatmap.HitObjects[i - 1];
addDelayedMovements(h, prev); addDelayedMovements(h, prev);
} }
addHitObjectReplay(h); addHitObjectReplay(h);
} }
return Replay;
} }
private void addDelayedMovements(OsuHitObject h, OsuHitObject prev) private void addDelayedMovements(OsuHitObject h, OsuHitObject prev)
@ -119,9 +121,9 @@ namespace osu.Game.Rulesets.Osu.Replays
{ {
calcSpinnerStartPosAndDirection(Frames[Frames.Count - 1].Position, out startPosition, out spinnerDirection); calcSpinnerStartPosAndDirection(Frames[Frames.Count - 1].Position, out startPosition, out spinnerDirection);
Vector2 spinCentreOffset = spinner_centre - Frames[Frames.Count - 1].Position; Vector2 spinCentreOffset = SPINNER_CENTRE - Frames[Frames.Count - 1].Position;
if (spinCentreOffset.Length > spin_radius) if (spinCentreOffset.Length > SPIN_RADIUS)
{ {
// If moving in from the outside, don't ease out (default eases out). This means auto will "start" spinning immediately after moving into position. // If moving in from the outside, don't ease out (default eases out). This means auto will "start" spinning immediately after moving into position.
easing = EasingTypes.In; easing = EasingTypes.In;
@ -139,19 +141,22 @@ namespace osu.Game.Rulesets.Osu.Replays
addHitObjectClickFrames(h, startPosition, spinnerDirection); addHitObjectClickFrames(h, startPosition, spinnerDirection);
} }
#endregion
#region Helper subroutines
private static void calcSpinnerStartPosAndDirection(Vector2 prevPos, out Vector2 startPosition, out float spinnerDirection) private static void calcSpinnerStartPosAndDirection(Vector2 prevPos, out Vector2 startPosition, out float spinnerDirection)
{ {
Vector2 spinCentreOffset = spinner_centre - prevPos; Vector2 spinCentreOffset = SPINNER_CENTRE - prevPos;
float distFromCentre = spinCentreOffset.Length; float distFromCentre = spinCentreOffset.Length;
float distToTangentPoint = (float)Math.Sqrt(distFromCentre * distFromCentre - spin_radius * spin_radius); float distToTangentPoint = (float)Math.Sqrt(distFromCentre * distFromCentre - SPIN_RADIUS * SPIN_RADIUS);
if (distFromCentre > spin_radius) if (distFromCentre > SPIN_RADIUS)
{ {
// Previous cursor position was outside spin circle, set startPosition to the tangent point. // Previous cursor position was outside spin circle, set startPosition to the tangent point.
// Angle between centre offset and tangent point offset. // Angle between centre offset and tangent point offset.
float angle = (float)Math.Asin(spin_radius / distFromCentre); float angle = (float)Math.Asin(SPIN_RADIUS / distFromCentre);
if (angle > 0) if (angle > 0)
{ {
@ -176,13 +181,13 @@ namespace osu.Game.Rulesets.Osu.Replays
else if (spinCentreOffset.Length > 0) else if (spinCentreOffset.Length > 0)
{ {
// Previous cursor position was inside spin circle, set startPosition to the nearest point on spin circle. // Previous cursor position was inside spin circle, set startPosition to the nearest point on spin circle.
startPosition = spinner_centre - spinCentreOffset * (spin_radius / spinCentreOffset.Length); startPosition = SPINNER_CENTRE - spinCentreOffset * (SPIN_RADIUS / spinCentreOffset.Length);
spinnerDirection = 1; spinnerDirection = 1;
} }
else else
{ {
// Degenerate case where cursor position is exactly at the centre of the spin circle. // Degenerate case where cursor position is exactly at the centre of the spin circle.
startPosition = spinner_centre + new Vector2(0, -spin_radius); startPosition = SPINNER_CENTRE + new Vector2(0, -SPIN_RADIUS);
spinnerDirection = 1; spinnerDirection = 1;
} }
} }
@ -283,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Replays
{ {
Spinner s = h as Spinner; Spinner s = h as Spinner;
Vector2 difference = startPosition - spinner_centre; Vector2 difference = startPosition - SPINNER_CENTRE;
float radius = difference.Length; float radius = difference.Length;
float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X); float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X);
@ -294,12 +299,12 @@ namespace osu.Game.Rulesets.Osu.Replays
{ {
t = applyModsToTime(j - h.StartTime) * spinnerDirection; t = applyModsToTime(j - h.StartTime) * spinnerDirection;
Vector2 pos = spinner_centre + circlePosition(t / 20 + angle, spin_radius); Vector2 pos = SPINNER_CENTRE + circlePosition(t / 20 + angle, SPIN_RADIUS);
addFrameToReplay(new ReplayFrame((int)j, pos.X, pos.Y, button)); addFrameToReplay(new ReplayFrame((int)j, pos.X, pos.Y, button));
} }
t = applyModsToTime(s.EndTime - h.StartTime) * spinnerDirection; t = applyModsToTime(s.EndTime - h.StartTime) * spinnerDirection;
Vector2 endPosition = spinner_centre + circlePosition(t / 20 + angle, spin_radius); Vector2 endPosition = SPINNER_CENTRE + circlePosition(t / 20 + angle, SPIN_RADIUS);
addFrameToReplay(new ReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button)); addFrameToReplay(new ReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button));
@ -323,5 +328,7 @@ namespace osu.Game.Rulesets.Osu.Replays
if (Frames[Frames.Count - 1].Time <= endFrame.Time) if (Frames[Frames.Count - 1].Time <= endFrame.Time)
addFrameToReplay(endFrame); addFrameToReplay(endFrame);
} }
#endregion
} }
} }

View File

@ -2,70 +2,52 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK; using OpenTK;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Rulesets.Osu.Replays namespace osu.Game.Rulesets.Osu.Replays
{ {
public abstract class OsuAutoReplayBase : Replay public abstract class OsuAutoGeneratorBase : AutoGenerator<OsuHitObject>
{ {
#region Constants #region Constants
/// <summary> /// <summary>
/// Constants (for spinners). /// Constants (for spinners).
/// </summary> /// </summary>
protected static readonly Vector2 spinner_centre = new Vector2(256, 192); protected static readonly Vector2 SPINNER_CENTRE = new Vector2(256, 192);
protected const float spin_radius = 50; protected const float SPIN_RADIUS = 50;
/// <summary>
/// The beatmap we're making a replay out of.
/// </summary>
protected readonly Beatmap<OsuHitObject> beatmap;
/// <summary> /// <summary>
/// The time in ms between each ReplayFrame. /// The time in ms between each ReplayFrame.
/// </summary> /// </summary>
protected double frameDelay; protected readonly double frameDelay;
#endregion #endregion
#region Construction #region Construction / Initialisation
public OsuAutoReplayBase(Beatmap<OsuHitObject> beatmap) protected Replay Replay;
{ protected List<ReplayFrame> Frames => Replay.Frames;
this.beatmap = beatmap;
Initialise();
CreateAutoReplay();
}
/// <summary> protected OsuAutoGeneratorBase(Beatmap<OsuHitObject> beatmap)
/// Initialise this instance. Called before CreateAutoReplay. : base(beatmap)
/// </summary>
protected virtual void Initialise()
{ {
User = new User Replay = new Replay
{ {
Username = @"Autoplay", User = new User
{
Username = @"Autoplay",
}
}; };
// 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. // 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); frameDelay = applyModsToRate(1000.0 / 60.0);
} }
/// <summary>
/// Creates the auto replay. Every OsuAutoReplayBase subclass should implement this!
/// </summary>
protected abstract void CreateAutoReplay();
#endregion #endregion
#region Utilities #region Utilities

View File

@ -82,8 +82,8 @@
<Compile Include="Objects\Spinner.cs" /> <Compile Include="Objects\Spinner.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Mods\OsuMod.cs" /> <Compile Include="Mods\OsuMod.cs" />
<Compile Include="Replays\OsuAutoReplay.cs" /> <Compile Include="Replays\OsuAutoGenerator.cs" />
<Compile Include="Replays\OsuAutoReplayBase.cs" /> <Compile Include="Replays\OsuAutoGeneratorBase.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj"> <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">

View File

@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
protected override Score CreateReplayScore(Beatmap<TaikoHitObject> beatmap) => new Score protected override Score CreateReplayScore(Beatmap<TaikoHitObject> beatmap) => new Score
{ {
User = new User { Username = "mekkadosu!" }, User = new User { Username = "mekkadosu!" },
Replay = new TaikoAutoReplay(beatmap) Replay = new TaikoAutoReplay(beatmap).Generate(),
}; };
} }
} }

View File

@ -1,7 +1,8 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -9,29 +10,28 @@ using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Taiko.Replays namespace osu.Game.Rulesets.Taiko.Replays
{ {
public class TaikoAutoReplay : Replay public class TaikoAutoReplay : AutoGenerator<TaikoHitObject>
{ {
private const double swell_hit_speed = 50; private const double swell_hit_speed = 50;
private readonly Beatmap<TaikoHitObject> beatmap;
public TaikoAutoReplay(Beatmap<TaikoHitObject> beatmap) public TaikoAutoReplay(Beatmap<TaikoHitObject> beatmap)
: base(beatmap)
{ {
this.beatmap = beatmap;
createAutoReplay();
} }
private void createAutoReplay() protected Replay Replay;
protected List<ReplayFrame> Frames => Replay.Frames;
public override Replay Generate()
{ {
bool hitButton = true; bool hitButton = true;
Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None)); Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None));
Frames.Add(new ReplayFrame(beatmap.HitObjects[0].StartTime - 1000, null, null, ReplayButtonState.None)); Frames.Add(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, null, null, ReplayButtonState.None));
for (int i = 0; i < beatmap.HitObjects.Count; i++) for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{ {
TaikoHitObject h = beatmap.HitObjects[i]; TaikoHitObject h = Beatmap.HitObjects[i];
ReplayButtonState button; ReplayButtonState button;
@ -105,15 +105,17 @@ namespace osu.Game.Rulesets.Taiko.Replays
Frames.Add(new ReplayFrame(endTime + KEY_UP_DELAY, null, null, ReplayButtonState.None)); Frames.Add(new ReplayFrame(endTime + KEY_UP_DELAY, null, null, ReplayButtonState.None));
if (i < beatmap.HitObjects.Count - 1) if (i < Beatmap.HitObjects.Count - 1)
{ {
double waitTime = beatmap.HitObjects[i + 1].StartTime - 1000; double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
if (waitTime > endTime) if (waitTime > endTime)
Frames.Add(new ReplayFrame(waitTime, null, null, ReplayButtonState.None)); Frames.Add(new ReplayFrame(waitTime, null, null, ReplayButtonState.None));
} }
hitButton = !hitButton; hitButton = !hitButton;
} }
return Replay;
} }
} }
} }

View File

@ -0,0 +1,36 @@
using osu.Game.Rulesets.Objects;
using osu.Game.Beatmaps;
namespace osu.Game.Rulesets.Replays
{
public abstract class AutoGenerator<T> : IAutoGenerator
where T : HitObject
{
/// <summary>
/// Creates the auto replay and returns it.
/// Every subclass of OsuAutoGeneratorBase should implement this!
/// </summary>
public abstract Replay Generate();
#region Parameters
/// <summary>
/// The beatmap we're making.
/// </summary>
protected Beatmap<T> Beatmap;
#endregion
public AutoGenerator(Beatmap<T> beatmap)
{
Beatmap = beatmap;
}
#region Constants
// Shared amongst all modes
protected const double KEY_UP_DELAY = 50;
#endregion
}
}

View File

@ -0,0 +1,9 @@
using osu.Game.Rulesets.Replays;
namespace osu.Game
{
public interface IAutoGenerator
{
Replay Generate();
}
}

View File

@ -8,8 +8,6 @@ namespace osu.Game.Rulesets.Replays
{ {
public class Replay public class Replay
{ {
protected const double KEY_UP_DELAY = 50;
public User User; public User User;
public List<ReplayFrame> Frames = new List<ReplayFrame>(); public List<ReplayFrame> Frames = new List<ReplayFrame>();

View File

@ -421,6 +421,8 @@
<Compile Include="Screens\Select\BeatmapDetailArea.cs" /> <Compile Include="Screens\Select\BeatmapDetailArea.cs" />
<Compile Include="Graphics\UserInterface\OsuTabControlCheckbox.cs" /> <Compile Include="Graphics\UserInterface\OsuTabControlCheckbox.cs" />
<Compile Include="Screens\Select\BeatmapDetailAreaTabControl.cs" /> <Compile Include="Screens\Select\BeatmapDetailAreaTabControl.cs" />
<Compile Include="Rulesets\Replays\IAutoGenerator.cs" />
<Compile Include="Rulesets\Replays\AutoGenerator.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj"> <ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">