Skip to content

Instantly share code, notes, and snippets.

@peppy
Created August 24, 2011 07:18
Show Gist options
  • Save peppy/1167470 to your computer and use it in GitHub Desktop.
Save peppy/1167470 to your computer and use it in GitHub Desktop.
osu! stacking algorithm
/* Stacking used in osu! formats v6+. (c) peppy 2011
* Versions previous to 6 had incorrect stacking, but is maintained for scoring purposes.
* For non-osu! clones there should be no problem using the following alogrithm for all format versions.
*/
StackOffset = HitObjectRadius / 10; //ymmv
Vector2 stackVector = new Vector2(StackOffset, StackOffset);
const int STACK_LENIENCE = 3;
//Reverse pass for stack calculation.
for (int i = objects.Count - 1; i > 0; 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.
*/
HitObject objectI = objects[i];
if (objectI.StackCount != 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)
{
HitObject objectN = objects[n];
if (objectN is Spinner) continue;
HitObjectSpannable spanN = objectN as HitObjectSpannable;
if (objectI.StartTime - (DifficultyManager.PreEmpt * beatmap.StackLeniency) > objectN.EndTime)
//We are no longer within stacking range of the previous object.
break;
/* 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 (spanN != null && pMathHelper.Distance(spanN.EndPosition, objectI.Position) < STACK_LENIENCE)
{
int offset = objectI.StackCount - objectN.StackCount + 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).
if (pMathHelper.Distance(spanN.EndPosition, objects[j].Position) < STACK_LENIENCE)
objects[j].StackCount -= 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 (pMathHelper.Distance(objectN.Position, objectI.Position) < STACK_LENIENCE)
{
//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.StackCount = objectI.StackCount + 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 >= 0)
{
HitObject objectN = objects[n];
if (objectN is Spinner) continue;
HitObjectSpannable spanN = objectN as HitObjectSpannable;
if (objectI.StartTime - (DifficultyManager.PreEmpt * beatmap.StackLeniency) > objectN.StartTime)
//We are no longer within stacking range of the previous object.
break;
if (pMathHelper.Distance((spanN != null ? spanN.EndPosition : objectN.Position), objectI.Position) < STACK_LENIENCE)
{
objectN.StackCount = objectI.StackCount + 1;
objectI = objectN;
}
}
}
}
@KevHere
Copy link

KevHere commented Jan 27, 2012

Dammit peppy, why did you have to make slider stacking so freaking hard. I figured it out after hours of work without this, then I FIND THIS SHIT. Reversing it was hard enough, why didn't you just send me this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment