diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index cdaa8fa3d5..3c2c5d7759 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -35,18 +35,18 @@ namespace osu.Game.Rulesets.Osu.Mods rng = new Random((int)Seed.Value); - var positionModifier = new OsuHitObjectPositionModifier(osuBeatmap.HitObjects); + var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects); float rateOfChangeMultiplier = 0; - foreach (var positionInfo in positionModifier.ObjectPositionInfos) + foreach (var positionInfo in positionInfos) { // rateOfChangeMultiplier only changes every 5 iterations in a combo // to prevent shaky-line-shaped streams if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; - if (positionInfo == positionModifier.ObjectPositionInfos.First()) + if (positionInfo == positionInfos.First()) { positionInfo.DistanceFromPrevious = (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2; positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - positionModifier.ApplyModifications(); + osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos); } } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 97a4b14a62..da73c2addb 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - public static class OsuHitObjectGenerationUtils + public static partial class OsuHitObjectGenerationUtils { // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The closer the hit objects draw to the border, the sharper the turn diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs similarity index 84% rename from osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs rename to osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 32f547dfe7..2a735c89d9 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectPositionModifier.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -13,10 +13,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Utils { - /// - /// Places hit objects according to information in while keeping objects inside the playfield. - /// - public class OsuHitObjectPositionModifier + public static partial class OsuHitObjectGenerationUtils { /// /// Number of previous hitobjects to be shifted together when an object is being moved. @@ -25,24 +22,15 @@ namespace osu.Game.Rulesets.Osu.Utils private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; - private readonly List hitObjects; - - private readonly List objectPositionInfos = new List(); - /// - /// Contains information specifying how each hit object should be placed. - /// The default values correspond to how objects are originally placed in the beatmap. + /// Generate a list of s containing information for how the given list of + /// s are positioned. /// - public IReadOnlyList ObjectPositionInfos => objectPositionInfos; - - public OsuHitObjectPositionModifier(List hitObjects) - { - this.hitObjects = hitObjects; - populateObjectPositionInfos(); - } - - private void populateObjectPositionInfos() + /// A list of s to process. + /// A list of s describing how each hit object is positioned relative to the previous one. + public static List GeneratePositionInfos(IEnumerable hitObjects) { + var positionInfos = new List(); Vector2 previousPosition = playfield_centre; float previousAngle = 0; @@ -52,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Utils float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); float relativeAngle = absoluteAngle - previousAngle; - objectPositionInfos.Add(new ObjectPositionInfo(hitObject) + positionInfos.Add(new ObjectPositionInfo(hitObject) { RelativeAngle = relativeAngle, DistanceFromPrevious = relativePosition.Length @@ -61,18 +49,23 @@ namespace osu.Game.Rulesets.Osu.Utils previousPosition = hitObject.EndPosition; previousAngle = absoluteAngle; } + + return positionInfos; } /// - /// Reposition the hit objects according to the information in . + /// Reposition the hit objects according to the information in . /// - public void ApplyModifications() + /// + /// The repositioned hit objects. + public static List RepositionHitObjects(IEnumerable objectPositionInfos) { + List positionInfos = objectPositionInfos.Cast().ToList(); ObjectPositionInfo? previous = null; - for (int i = 0; i < objectPositionInfos.Count; i++) + for (int i = 0; i < positionInfos.Count; i++) { - var current = objectPositionInfos[i]; + var current = positionInfos[i]; var hitObject = current.HitObject; if (hitObject is Spinner) @@ -81,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Utils continue; } - computeModifiedPosition(current, previous, i > 1 ? objectPositionInfos[i - 2] : null); + computeModifiedPosition(current, previous, i > 1 ? positionInfos[i - 2] : null); // Move hit objects back into the playfield if they are outside of it Vector2 shift = Vector2.Zero; @@ -104,9 +97,9 @@ namespace osu.Game.Rulesets.Osu.Utils for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) { // only shift hit circles - if (!(hitObjects[j] is HitCircle)) break; + if (!(positionInfos[j].HitObject is HitCircle)) break; - toBeShifted.Add(hitObjects[j]); + toBeShifted.Add(positionInfos[j].HitObject); } if (toBeShifted.Count > 0) @@ -115,6 +108,8 @@ namespace osu.Game.Rulesets.Osu.Utils previous = current; } + + return positionInfos.Select(p => p.HitObject).ToList(); } /// @@ -123,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// The representing the hit object to have the modified position computed for. /// The representing the hit object immediately preceding the current one. /// The representing the hit object immediately preceding the one. - private void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) + private static void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) { float previousAbsoluteAngle = 0f; @@ -143,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Utils Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre; - posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); + posRelativeToPrev = RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); current.PositionModified = lastEndPosition + posRelativeToPrev; } @@ -152,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Move the modified position of a hit circle so that it fits inside the playfield. /// /// The deviation from the original modified position in order to fit within the playfield. - private Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) + private static Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) { var previousPosition = objectPositionInfo.PositionModified; objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( @@ -169,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Moves the and all necessary nested s into the if they aren't already. /// /// The deviation from the original modified position in order to fit within the playfield. - private Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) + private static Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) { var possibleMovementBounds = calculatePossibleMovementBounds(slider); @@ -199,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// The list of hit objects to be shifted. /// The amount to be shifted. - private void applyDecreasingShift(IList hitObjects, Vector2 shift) + private static void applyDecreasingShift(IList hitObjects, Vector2 shift) { for (int i = 0; i < hitObjects.Count; i++) { @@ -219,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - private RectangleF calculatePossibleMovementBounds(Slider slider) + private static RectangleF calculatePossibleMovementBounds(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); @@ -266,7 +261,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// whose nested s and s should be shifted /// The the 's nested s and s should be shifted by - private void shiftNestedObjects(Slider slider, Vector2 shift) + private static void shiftNestedObjects(Slider slider, Vector2 shift) { foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat)) { @@ -283,7 +278,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// The position to be clamped. /// The minimum distance allowed from playfield edges. /// The clamped position. - private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) + private static Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) { return new Vector2( Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding),