diff --git a/osu.Game.Rulesets.Osu/Mods/OsuMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs index b3a7fc5878..ff277dea1f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuMod.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected override Score CreateReplayScore(Beatmap beatmap) => new Score { - Replay = new OsuAutoReplay(beatmap) + Replay = new OsuAutoGenerator(beatmap).Generate() }; } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoReplay.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs similarity index 87% rename from osu.Game.Rulesets.Osu/Replays/OsuAutoReplay.cs rename to osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 4d06af7b92..b4205f9764 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoReplay.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -7,80 +7,82 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using System; -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.Users; namespace osu.Game.Rulesets.Osu.Replays { - public class OsuAutoReplay : OsuAutoReplayBase + public class OsuAutoGenerator : OsuAutoGeneratorBase { - // Options + #region Parameters /// /// If delayed movements should be used, causing the cursor to stay on each hitobject for as long as possible. /// Mainly for Autopilot. /// - public readonly bool DelayedMovements; // ModManager.CheckActive(Mods.Relax2); + public bool DelayedMovements; // ModManager.CheckActive(Mods.Relax2); + #endregion - // Local constants + #region Constants /// /// The "reaction time" in ms between "seeing" a new hit object and moving to "react" to it. /// - private double reactionTime; + private readonly double reactionTime; /// /// What easing to use when moving between hitobjects /// private EasingTypes preferredEasing => DelayedMovements ? EasingTypes.InOutCubic : EasingTypes.Out; + #endregion + + #region Construction / Initialisation + + public OsuAutoGenerator(Beatmap beatmap) + : base(beatmap) + { + // Already superhuman, but still somewhat realistic + reactionTime = applyModsToRate(100); + } + + #endregion + + #region Generator + /// /// 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. /// This keeps track of the button previously used for alt/singletap logic. /// - private int buttonIndex; - public OsuAutoReplay(Beatmap beatmap) - : base(beatmap) - { - } - - protected override void Initialise() - { - base.Initialise(); - - // Already superhuman, but still somewhat realistic - reactionTime = applyModsToRate(100); - } - - protected override void CreateAutoReplay() + public override Replay Generate() { buttonIndex = 0; 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 - 1000, 256, 192, 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)); - 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) { - OsuHitObject prev = beatmap.HitObjects[i - 1]; + OsuHitObject prev = Beatmap.HitObjects[i - 1]; addDelayedMovements(h, prev); } addHitObjectReplay(h); } + + return Replay; } 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); - 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. easing = EasingTypes.In; @@ -139,19 +141,22 @@ namespace osu.Game.Rulesets.Osu.Replays addHitObjectClickFrames(h, startPosition, spinnerDirection); } + #endregion + + #region Helper subroutines 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 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. // 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) { @@ -176,13 +181,13 @@ namespace osu.Game.Rulesets.Osu.Replays else if (spinCentreOffset.Length > 0) { // 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; } else { // 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; } } @@ -283,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Replays { Spinner s = h as Spinner; - Vector2 difference = startPosition - spinner_centre; + Vector2 difference = startPosition - SPINNER_CENTRE; float radius = difference.Length; 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; - 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)); } 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)); @@ -323,5 +328,7 @@ namespace osu.Game.Rulesets.Osu.Replays if (Frames[Frames.Count - 1].Time <= endFrame.Time) addFrameToReplay(endFrame); } + + #endregion } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoReplayBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs similarity index 65% rename from osu.Game.Rulesets.Osu/Replays/OsuAutoReplayBase.cs rename to osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 37d2a60c82..9f724ac3b2 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoReplayBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -2,70 +2,52 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; using System; 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.Users; namespace osu.Game.Rulesets.Osu.Replays { - public abstract class OsuAutoReplayBase : Replay + public abstract class OsuAutoGeneratorBase : AutoGenerator { #region Constants /// /// Constants (for spinners). /// - protected static readonly Vector2 spinner_centre = new Vector2(256, 192); - protected const float spin_radius = 50; - - /// - /// The beatmap we're making a replay out of. - /// - protected readonly Beatmap beatmap; + protected static readonly Vector2 SPINNER_CENTRE = new Vector2(256, 192); + protected const float SPIN_RADIUS = 50; /// /// The time in ms between each ReplayFrame. /// - protected double frameDelay; + protected readonly double frameDelay; #endregion - #region Construction + #region Construction / Initialisation - public OsuAutoReplayBase(Beatmap beatmap) - { - this.beatmap = beatmap; - Initialise(); - CreateAutoReplay(); - } + protected Replay Replay; + protected List Frames => Replay.Frames; - /// - /// Initialise this instance. Called before CreateAutoReplay. - /// - protected virtual void Initialise() + protected OsuAutoGeneratorBase(Beatmap beatmap) + : base(beatmap) { - 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. frameDelay = applyModsToRate(1000.0 / 60.0); } - /// - /// Creates the auto replay. Every OsuAutoReplayBase subclass should implement this! - /// - protected abstract void CreateAutoReplay(); - #endregion #region Utilities diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 8041dd546d..8974b1bcbd 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -82,8 +82,8 @@ - - + + diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoMod.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoMod.cs index 0b8492ef8c..44430fe6bc 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoMod.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoMod.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Mods protected override Score CreateReplayScore(Beatmap beatmap) => new Score { User = new User { Username = "mekkadosu!" }, - Replay = new TaikoAutoReplay(beatmap) + Replay = new TaikoAutoReplay(beatmap).Generate(), }; } } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoReplay.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoReplay.cs index d78e8af589..0af8e2822b 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoReplay.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoReplay.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . +// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; @@ -9,29 +10,28 @@ using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Taiko.Replays { - public class TaikoAutoReplay : Replay + public class TaikoAutoReplay : AutoGenerator { private const double swell_hit_speed = 50; - private readonly Beatmap beatmap; - public TaikoAutoReplay(Beatmap beatmap) + : base(beatmap) { - this.beatmap = beatmap; - - createAutoReplay(); } - private void createAutoReplay() + protected Replay Replay; + protected List Frames => Replay.Frames; + + public override Replay Generate() { bool hitButton = true; 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; @@ -105,15 +105,17 @@ namespace osu.Game.Rulesets.Taiko.Replays 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) Frames.Add(new ReplayFrame(waitTime, null, null, ReplayButtonState.None)); } hitButton = !hitButton; } + + return Replay; } } } diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs new file mode 100644 index 0000000000..dee21b325b --- /dev/null +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -0,0 +1,36 @@ +using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Replays +{ + public abstract class AutoGenerator : IAutoGenerator + where T : HitObject + { + /// + /// Creates the auto replay and returns it. + /// Every subclass of OsuAutoGeneratorBase should implement this! + /// + public abstract Replay Generate(); + + #region Parameters + + /// + /// The beatmap we're making. + /// + protected Beatmap Beatmap; + + #endregion + + public AutoGenerator(Beatmap beatmap) + { + Beatmap = beatmap; + } + + #region Constants + + // Shared amongst all modes + protected const double KEY_UP_DELAY = 50; + + #endregion + } +} diff --git a/osu.Game/Rulesets/Replays/IAutoGenerator.cs b/osu.Game/Rulesets/Replays/IAutoGenerator.cs new file mode 100644 index 0000000000..8dd3fe0abf --- /dev/null +++ b/osu.Game/Rulesets/Replays/IAutoGenerator.cs @@ -0,0 +1,9 @@ +using osu.Game.Rulesets.Replays; + +namespace osu.Game +{ + public interface IAutoGenerator + { + Replay Generate(); + } +} diff --git a/osu.Game/Rulesets/Replays/Replay.cs b/osu.Game/Rulesets/Replays/Replay.cs index 36e1b24e73..89e19434f0 100644 --- a/osu.Game/Rulesets/Replays/Replay.cs +++ b/osu.Game/Rulesets/Replays/Replay.cs @@ -8,8 +8,6 @@ namespace osu.Game.Rulesets.Replays { public class Replay { - protected const double KEY_UP_DELAY = 50; - public User User; public List Frames = new List(); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index cb491055c4..334ecf639e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -421,6 +421,8 @@ + +