mirror of
https://github.com/ppy/osu.git
synced 2026-05-20 18:51:17 +08:00
Merge pull request #22499 from ekrctb/catch-hyperdash-stable-sort
Use stable sort for catch hyperdash generation
This commit is contained in:
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
for (int i = 0; i < 11; i++)
|
||||
{
|
||||
int count = i + 1;
|
||||
AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count);
|
||||
@@ -104,12 +104,22 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
})
|
||||
}, 1);
|
||||
|
||||
createObjects(() => new Fruit { X = right_x }, count: 2, spacing: 0, spacingAfterGroup: 400);
|
||||
createObjects(() => new TestJuiceStream(left_x)
|
||||
{
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(Vector2.Zero),
|
||||
new PathControlPoint(new Vector2(0, 300))
|
||||
})
|
||||
}, count: 1, spacingAfterGroup: 150);
|
||||
createObjects(() => new Fruit { X = left_x }, count: 1, spacing: 0, spacingAfterGroup: 400);
|
||||
createObjects(() => new Fruit { X = right_x }, count: 2, spacing: 0);
|
||||
|
||||
return beatmap;
|
||||
|
||||
void createObjects(Func<CatchHitObject> createObject, int count = 3)
|
||||
void createObjects(Func<CatchHitObject> createObject, int count = 3, float spacing = 140, float spacingAfterGroup = 700)
|
||||
{
|
||||
const float spacing = 140;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var hitObject = createObject();
|
||||
@@ -117,7 +127,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
beatmap.HitObjects.Add(hitObject);
|
||||
}
|
||||
|
||||
startTime += 700;
|
||||
startTime += spacingAfterGroup;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
{
|
||||
@@ -38,5 +39,25 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate all <see cref="PalpableCatchHitObject"/>s, sorted by their start times.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If multiple objects have the same start time, the ordering is preserved (it is a stable sorting).
|
||||
/// </remarks>
|
||||
public static IEnumerable<PalpableCatchHitObject> GetPalpableObjects(IEnumerable<HitObject> hitObjects)
|
||||
{
|
||||
return hitObjects.SelectMany(selectPalpableObjects).OrderBy(h => h.StartTime);
|
||||
|
||||
IEnumerable<PalpableCatchHitObject> selectPalpableObjects(HitObject h)
|
||||
{
|
||||
if (h is PalpableCatchHitObject palpable)
|
||||
yield return palpable;
|
||||
|
||||
foreach (var nested in h.NestedHitObjects.OfType<PalpableCatchHitObject>())
|
||||
yield return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
@@ -208,24 +207,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
|
||||
private static void initialiseHyperDash(IBeatmap beatmap)
|
||||
{
|
||||
List<PalpableCatchHitObject> palpableObjects = new List<PalpableCatchHitObject>();
|
||||
|
||||
foreach (var currentObject in beatmap.HitObjects)
|
||||
{
|
||||
if (currentObject is Fruit fruitObject)
|
||||
palpableObjects.Add(fruitObject);
|
||||
|
||||
if (currentObject is JuiceStream)
|
||||
{
|
||||
foreach (var juice in currentObject.NestedHitObjects)
|
||||
{
|
||||
if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet))
|
||||
palpableObjects.Add(palpableObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||
var palpableObjects = CatchBeatmap.GetPalpableObjects(beatmap.HitObjects)
|
||||
.Where(h => h is Fruit || (h is Droplet && h is not TinyDroplet))
|
||||
.ToArray();
|
||||
|
||||
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) / 2;
|
||||
|
||||
@@ -237,7 +221,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
int lastDirection = 0;
|
||||
double lastExcess = halfCatcherWidth;
|
||||
|
||||
for (int i = 0; i < palpableObjects.Count - 1; i++)
|
||||
for (int i = 0; i < palpableObjects.Length - 1; i++)
|
||||
{
|
||||
var currentObject = palpableObjects[i];
|
||||
var nextObject = palpableObjects[i + 1];
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Catch.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
@@ -56,13 +57,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||
|
||||
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
|
||||
foreach (var hitObject in beatmap.HitObjects
|
||||
.SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects.AsEnumerable() : new[] { obj })
|
||||
.Cast<CatchHitObject>()
|
||||
.OrderBy(x => x.StartTime))
|
||||
foreach (var hitObject in CatchBeatmap.GetPalpableObjects(beatmap.HitObjects))
|
||||
{
|
||||
// We want to only consider fruits that contribute to the combo.
|
||||
if (hitObject is BananaShower || hitObject is TinyDroplet)
|
||||
if (hitObject is Banana || hitObject is TinyDroplet)
|
||||
continue;
|
||||
|
||||
if (lastObject != null)
|
||||
|
||||
@@ -126,6 +126,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||
|
||||
private double? lastHyperDashStartTime;
|
||||
private double hyperDashModifier = 1;
|
||||
private int hyperDashDirection;
|
||||
private float hyperDashTargetPosition;
|
||||
@@ -233,16 +234,23 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
// droplet doesn't affect the catcher state
|
||||
if (hitObject is TinyDroplet) return;
|
||||
|
||||
if (result.IsHit && hitObject.HyperDashTarget is CatchHitObject target)
|
||||
// if a hyper fruit was already handled this frame, just go where it says to go.
|
||||
// this special-cases some aspire maps that have doubled-up objects (one hyper, one not) at the same time instant.
|
||||
// handling this "properly" elsewhere is impossible as there is no feasible way to ensure
|
||||
// that the hyperfruit gets judged second (especially if it coincides with a last fruit in a juice stream).
|
||||
if (lastHyperDashStartTime != Time.Current)
|
||||
{
|
||||
double timeDifference = target.StartTime - hitObject.StartTime;
|
||||
double positionDifference = target.EffectiveX - X;
|
||||
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
|
||||
if (result.IsHit && hitObject.HyperDashTarget is CatchHitObject target)
|
||||
{
|
||||
double timeDifference = target.StartTime - hitObject.StartTime;
|
||||
double positionDifference = target.EffectiveX - X;
|
||||
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
|
||||
|
||||
SetHyperDashState(Math.Abs(velocity) / BASE_DASH_SPEED, target.EffectiveX);
|
||||
SetHyperDashState(Math.Abs(velocity) / BASE_DASH_SPEED, target.EffectiveX);
|
||||
}
|
||||
else
|
||||
SetHyperDashState();
|
||||
}
|
||||
else
|
||||
SetHyperDashState();
|
||||
|
||||
if (result.IsHit)
|
||||
CurrentState = hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle;
|
||||
@@ -292,6 +300,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
if (wasHyperDashing)
|
||||
runHyperDashStateTransition(false);
|
||||
|
||||
lastHyperDashStartTime = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -301,6 +311,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
if (!wasHyperDashing)
|
||||
runHyperDashStateTransition(true);
|
||||
|
||||
lastHyperDashStartTime = Time.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user