mirror of
https://github.com/osukey/osukey.git
synced 2025-08-02 22:26:41 +09:00
Add hit object stacking.
This commit is contained in:
@ -28,7 +28,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
osuObject = h;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
Position = osuObject.Position;
|
||||
Position = osuObject.Position + h.StackOffset;
|
||||
Scale = new Vector2(osuObject.Scale);
|
||||
|
||||
Children = new Drawable[]
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
{
|
||||
body = new SliderBody(s)
|
||||
{
|
||||
Position = s.Position,
|
||||
Position = s.Position + s.StackOffset,
|
||||
PathWidth = s.Scale * 64,
|
||||
},
|
||||
bouncer1 = new SliderBouncer(s, false)
|
||||
@ -41,7 +41,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
},
|
||||
bouncer2 = new SliderBouncer(s, true)
|
||||
{
|
||||
Position = s.Position,
|
||||
Position = s.Position + s.StackOffset,
|
||||
Scale = new Vector2(s.Scale),
|
||||
},
|
||||
ball = new SliderBall(s)
|
||||
@ -51,7 +51,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
initialCircle = new DrawableHitCircle(new HitCircle
|
||||
{
|
||||
StartTime = s.StartTime,
|
||||
Position = s.Position,
|
||||
Position = s.Position + s.StackOffset,
|
||||
Scale = s.Scale,
|
||||
Colour = s.Colour,
|
||||
Sample = s.Sample,
|
||||
@ -89,11 +89,11 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
if (repeat % 2 == 1)
|
||||
progress = 1 - progress;
|
||||
|
||||
bouncer2.Position = slider.Curve.PositionAt(body.SnakedEnd ?? 0);
|
||||
bouncer2.Position = slider.Curve.PositionAt(body.SnakedEnd ?? 0) + slider.StackOffset;
|
||||
|
||||
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
|
||||
if (initialCircle.Judgement?.Result != HitResult.Hit)
|
||||
initialCircle.Position = slider.Curve.PositionAt(progress);
|
||||
initialCircle.Position = slider.Curve.PositionAt(progress) + slider.StackOffset;
|
||||
|
||||
components.ForEach(c => c.UpdateProgress(progress, repeat));
|
||||
}
|
||||
|
@ -17,6 +17,9 @@ namespace osu.Game.Modes.Osu.Objects
|
||||
|
||||
public virtual Vector2 EndPosition => Position;
|
||||
|
||||
public int StackHeight { get; set; }
|
||||
public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
|
||||
|
||||
public override void SetDefaultsFromBeatmap(Beatmap beatmap)
|
||||
{
|
||||
base.SetDefaultsFromBeatmap(beatmap);
|
||||
|
@ -3,19 +3,168 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Modes.Objects;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Modes.Osu.Objects.Drawables;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Modes.Osu.Objects
|
||||
{
|
||||
public class OsuHitObjectConverter : HitObjectConverter<OsuHitObject>
|
||||
{
|
||||
public override List<OsuHitObject> Convert(List<HitObject> input)
|
||||
public override List<OsuHitObject> Convert(Beatmap beatmap)
|
||||
{
|
||||
List<OsuHitObject> output = new List<OsuHitObject>();
|
||||
|
||||
foreach (HitObject h in input)
|
||||
foreach (HitObject h in beatmap.HitObjects)
|
||||
output.Add(h as OsuHitObject);
|
||||
|
||||
UpdateStacking(output, beatmap.BeatmapInfo?.StackLeniency ?? 0.7f);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static void UpdateStacking(List<OsuHitObject> hitObjects, float stackLeniency, int startIndex = 0, int endIndex = -1)
|
||||
{
|
||||
if (endIndex == -1)
|
||||
endIndex = hitObjects.Count - 1;
|
||||
|
||||
int stackDistance = 3;
|
||||
float stackThreshold = DrawableOsuHitObject.TIME_PREEMPT * stackLeniency;
|
||||
|
||||
// Reset stacking inside the update range
|
||||
for (int i = startIndex; i <= endIndex; i++)
|
||||
hitObjects[i].StackHeight = 0;
|
||||
|
||||
// Extend the end index to include objects they are stacked on
|
||||
int extendedEndIndex = endIndex;
|
||||
for (int i = endIndex; i >= startIndex; i--)
|
||||
{
|
||||
int stackBaseIndex = i;
|
||||
for (int n = stackBaseIndex + 1; n < hitObjects.Count; n++)
|
||||
{
|
||||
OsuHitObject stackBaseObject = hitObjects[stackBaseIndex];
|
||||
if (stackBaseObject is Spinner) break;
|
||||
|
||||
OsuHitObject objectN = hitObjects[n];
|
||||
if (objectN is Spinner) continue;
|
||||
|
||||
if (objectN.StartTime - stackBaseObject.EndTime > stackThreshold)
|
||||
//We are no longer within stacking range of the next object.
|
||||
break;
|
||||
|
||||
if (Vector2.Distance(stackBaseObject.Position, objectN.Position) < stackDistance ||
|
||||
(stackBaseObject is Slider && Vector2.Distance(stackBaseObject.EndPosition, objectN.Position) < stackDistance))
|
||||
{
|
||||
stackBaseIndex = n;
|
||||
|
||||
// HitObjects after the specified update range haven't been reset yet
|
||||
objectN.StackHeight = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (stackBaseIndex > extendedEndIndex)
|
||||
{
|
||||
extendedEndIndex = stackBaseIndex;
|
||||
if (extendedEndIndex == hitObjects.Count - 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Reverse pass for stack calculation.
|
||||
int extendedStartIndex = startIndex;
|
||||
for (int i = extendedEndIndex; i > startIndex; i--)
|
||||
{
|
||||
int n = i;
|
||||
/* We should check every note which has not yet got a stack.
|
||||
* Consider the case we have two interwound stacks and this will make sense.
|
||||
*
|
||||
* o <-1 o <-2
|
||||
* o <-3 o <-4
|
||||
*
|
||||
* We first process starting from 4 and handle 2,
|
||||
* then we come backwards on the i loop iteration until we reach 3 and handle 1.
|
||||
* 2 and 1 will be ignored in the i loop because they already have a stack value.
|
||||
*/
|
||||
|
||||
OsuHitObject objectI = hitObjects[i];
|
||||
if (objectI.StackHeight != 0 || objectI is Spinner) continue;
|
||||
|
||||
/* If this object is a hitcircle, then we enter this "special" case.
|
||||
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
|
||||
* Any other case is handled by the "is Slider" code below this.
|
||||
*/
|
||||
if (objectI is HitCircle)
|
||||
{
|
||||
while (--n >= 0)
|
||||
{
|
||||
OsuHitObject objectN = hitObjects[n];
|
||||
if (objectN is Spinner) continue;
|
||||
|
||||
if (objectI.StartTime - objectN.EndTime > stackThreshold)
|
||||
//We are no longer within stacking range of the previous object.
|
||||
break;
|
||||
|
||||
// HitObjects before the specified update range haven't been reset yet
|
||||
if (n < extendedStartIndex)
|
||||
{
|
||||
objectN.StackHeight = 0;
|
||||
extendedStartIndex = n;
|
||||
}
|
||||
|
||||
/* This is a special case where hticircles are moved DOWN and RIGHT (negative stacking) if they are under the *last* slider in a stacked pattern.
|
||||
* o==o <- slider is at original location
|
||||
* o <- hitCircle has stack of -1
|
||||
* o <- hitCircle has stack of -2
|
||||
*/
|
||||
if (objectN is Slider && Vector2.Distance(objectN.EndPosition, objectI.Position) < stackDistance)
|
||||
{
|
||||
int offset = objectI.StackHeight - objectN.StackHeight + 1;
|
||||
for (int j = n + 1; j <= i; j++)
|
||||
{
|
||||
//For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above).
|
||||
OsuHitObject objectJ = hitObjects[j];
|
||||
if (Vector2.Distance(objectN.EndPosition, objectJ.Position) < stackDistance)
|
||||
objectJ.StackHeight -= offset;
|
||||
}
|
||||
|
||||
//We have hit a slider. We should restart calculation using this as the new base.
|
||||
//Breaking here will mean that the slider still has StackCount of 0, so will be handled in the i-outer-loop.
|
||||
break;
|
||||
}
|
||||
|
||||
if (Vector2.Distance(objectN.Position, objectI.Position) < stackDistance)
|
||||
{
|
||||
//Keep processing as if there are no sliders. If we come across a slider, this gets cancelled out.
|
||||
//NOTE: Sliders with start positions stacking are a special case that is also handled here.
|
||||
|
||||
objectN.StackHeight = objectI.StackHeight + 1;
|
||||
objectI = objectN;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (objectI is Slider)
|
||||
{
|
||||
/* We have hit the first slider in a possible stack.
|
||||
* From this point on, we ALWAYS stack positive regardless.
|
||||
*/
|
||||
while (--n >= startIndex)
|
||||
{
|
||||
OsuHitObject objectN = hitObjects[n];
|
||||
if (objectN is Spinner) continue;
|
||||
|
||||
if (objectI.StartTime - objectN.StartTime > stackThreshold)
|
||||
//We are no longer within stacking range of the previous object.
|
||||
break;
|
||||
|
||||
if (Vector2.Distance(objectN.EndPosition, objectI.Position) < stackDistance)
|
||||
{
|
||||
objectN.StackHeight = objectI.StackHeight + 1;
|
||||
objectI = objectN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Modes.Osu
|
||||
{
|
||||
public override ScoreOverlay CreateScoreOverlay() => new OsuScoreOverlay();
|
||||
|
||||
public override HitRenderer CreateHitRendererWith(List<HitObject> objects) => new OsuHitRenderer { Objects = objects };
|
||||
public override HitRenderer CreateHitRendererWith(Beatmap beatmap) => new OsuHitRenderer { Beatmap = beatmap };
|
||||
|
||||
public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap) => new[]
|
||||
{
|
||||
|
Reference in New Issue
Block a user