1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-16 10:17:36 +08:00
osu-lazer/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

259 lines
12 KiB
C#
Raw Normal View History

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 17:19:50 +08:00
2019-01-07 16:56:31 +08:00
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
2017-04-18 15:05:58 +08:00
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
2018-04-13 17:19:50 +08:00
2017-04-18 15:05:58 +08:00
namespace osu.Game.Rulesets.Osu.Beatmaps
{
2018-08-17 09:04:00 +08:00
public class OsuBeatmapProcessor : BeatmapProcessor
{
2018-10-12 15:47:18 +08:00
private const int stack_distance = 3;
public OsuBeatmapProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
public override void PreProcess()
{
IHasComboInformation? lastObj = null;
// For sanity, ensures that both the first hitobject and the first hitobject after a spinner start a new combo.
// This is normally enforced by the legacy decoder, but is not enforced by the editor.
foreach (var obj in Beatmap.HitObjects.OfType<IHasComboInformation>())
{
if (obj is not Spinner && (lastObj == null || lastObj is Spinner))
obj.NewCombo = true;
lastObj = obj;
}
base.PreProcess();
}
public override void PostProcess()
{
base.PostProcess();
2018-04-13 17:19:50 +08:00
2018-10-12 15:47:18 +08:00
var osuBeatmap = (Beatmap<OsuHitObject>)Beatmap;
2018-04-13 17:19:50 +08:00
if (osuBeatmap.HitObjects.Count > 0)
{
// Reset stacking
foreach (var h in osuBeatmap.HitObjects)
h.StackHeight = 0;
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
applyStacking(osuBeatmap, 0, osuBeatmap.HitObjects.Count - 1);
else
applyStackingOld(osuBeatmap);
}
2018-10-12 15:47:18 +08:00
}
2019-01-07 16:56:31 +08:00
private void applyStacking(Beatmap<OsuHitObject> beatmap, int startIndex, int endIndex)
2018-10-12 15:47:18 +08:00
{
2019-01-07 17:13:40 +08:00
if (startIndex > endIndex) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be greater than {nameof(endIndex)}.");
if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be less than 0.");
if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be less than 0.");
2019-01-07 16:56:31 +08:00
int extendedEndIndex = endIndex;
2019-04-01 11:16:05 +08:00
2019-01-07 16:57:45 +08:00
if (endIndex < beatmap.HitObjects.Count - 1)
2017-04-18 13:24:16 +08:00
{
2019-01-07 16:57:45 +08:00
// Extend the end index to include objects they are stacked on
for (int i = endIndex; i >= startIndex; i--)
2017-04-18 13:24:16 +08:00
{
2019-01-07 16:57:45 +08:00
int stackBaseIndex = i;
2019-04-01 11:16:05 +08:00
2019-01-07 16:57:45 +08:00
for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++)
{
OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex];
if (stackBaseObject is Spinner) break;
2018-04-13 17:19:50 +08:00
2019-01-07 16:57:45 +08:00
OsuHitObject objectN = beatmap.HitObjects[n];
if (objectN is Spinner)
continue;
2018-04-13 17:19:50 +08:00
double endTime = stackBaseObject.GetEndTime();
2019-01-07 16:57:45 +08:00
double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
2018-04-13 17:19:50 +08:00
2019-01-07 16:57:45 +08:00
if (objectN.StartTime - endTime > stackThreshold)
2020-05-05 09:31:11 +08:00
// We are no longer within stacking range of the next object.
2019-01-07 16:57:45 +08:00
break;
2018-04-13 17:19:50 +08:00
2019-01-07 16:57:45 +08:00
if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance
|| (stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance))
2019-01-07 16:57:45 +08:00
{
stackBaseIndex = n;
2018-04-13 17:19:50 +08:00
2019-01-07 16:57:45 +08:00
// HitObjects after the specified update range haven't been reset yet
objectN.StackHeight = 0;
}
2017-04-18 13:24:16 +08:00
}
2018-04-13 17:19:50 +08:00
2019-01-07 16:57:45 +08:00
if (stackBaseIndex > extendedEndIndex)
{
extendedEndIndex = stackBaseIndex;
if (extendedEndIndex == beatmap.HitObjects.Count - 1)
break;
}
2017-04-18 13:24:16 +08:00
}
}
2018-04-13 17:19:50 +08:00
2020-05-05 09:31:11 +08:00
// Reverse pass for stack calculation.
2019-01-07 16:56:31 +08:00
int extendedStartIndex = startIndex;
2019-04-01 11:16:05 +08:00
2019-01-07 16:56:31 +08:00
for (int i = extendedEndIndex; i > startIndex; i--)
2017-04-18 13:24:16 +08:00
{
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.
*/
2018-04-13 17:19:50 +08:00
2017-04-18 13:24:16 +08:00
OsuHitObject objectI = beatmap.HitObjects[i];
if (objectI.StackHeight != 0 || objectI is Spinner) continue;
2018-04-13 17:19:50 +08:00
2018-07-02 13:28:56 +08:00
double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
2018-04-13 17:19:50 +08:00
2017-04-18 13:24:16 +08:00
/* 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.
*/
2017-04-18 13:24:16 +08:00
if (objectI is HitCircle)
{
while (--n >= 0)
{
OsuHitObject objectN = beatmap.HitObjects[n];
if (objectN is Spinner) continue;
2018-04-13 17:19:50 +08:00
double endTime = objectN.GetEndTime();
2018-04-13 17:19:50 +08:00
2017-04-18 13:24:16 +08:00
if (objectI.StartTime - endTime > stackThreshold)
2020-05-05 09:31:11 +08:00
// We are no longer within stacking range of the previous object.
2017-04-18 13:24:16 +08:00
break;
2018-04-13 17:19:50 +08:00
2017-04-18 13:24:16 +08:00
// HitObjects before the specified update range haven't been reset yet
if (n < extendedStartIndex)
{
objectN.StackHeight = 0;
extendedStartIndex = n;
}
2018-04-13 17:19:50 +08:00
2017-04-18 13:24:16 +08:00
/* 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 && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance)
2017-04-18 13:24:16 +08:00
{
int offset = objectI.StackHeight - objectN.StackHeight + 1;
2019-04-01 11:16:05 +08:00
2017-04-18 13:24:16 +08:00
for (int j = n + 1; j <= i; j++)
{
2020-05-05 09:31:11 +08:00
// For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above).
2017-04-18 13:24:16 +08:00
OsuHitObject objectJ = beatmap.HitObjects[j];
if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance)
2017-04-18 13:24:16 +08:00
objectJ.StackHeight -= offset;
}
2018-04-13 17:19:50 +08:00
2020-05-05 09:31:11 +08:00
// 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.
2017-04-18 13:24:16 +08:00
break;
}
2018-04-13 17:19:50 +08:00
if (Vector2Extensions.Distance(objectN.Position, objectI.Position) < stack_distance)
2017-04-18 13:24:16 +08:00
{
2020-05-05 09:31:11 +08:00
// Keep processing as if there are no sliders. If we come across a slider, this gets cancelled out.
2017-04-18 13:24:16 +08:00
//NOTE: Sliders with start positions stacking are a special case that is also handled here.
2018-04-13 17:19:50 +08:00
2017-04-18 13:24:16 +08:00
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.
*/
2019-01-07 16:56:31 +08:00
while (--n >= startIndex)
2017-04-18 13:24:16 +08:00
{
OsuHitObject objectN = beatmap.HitObjects[n];
if (objectN is Spinner) continue;
2018-04-13 17:19:50 +08:00
2017-04-18 13:24:16 +08:00
if (objectI.StartTime - objectN.StartTime > stackThreshold)
2020-05-05 09:31:11 +08:00
// We are no longer within stacking range of the previous object.
2017-04-18 13:24:16 +08:00
break;
2018-04-13 17:19:50 +08:00
if (Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance)
2017-04-18 13:24:16 +08:00
{
objectN.StackHeight = objectI.StackHeight + 1;
objectI = objectN;
}
}
}
}
}
2018-10-12 15:47:18 +08:00
private void applyStackingOld(Beatmap<OsuHitObject> beatmap)
{
for (int i = 0; i < beatmap.HitObjects.Count; i++)
{
OsuHitObject currHitObject = beatmap.HitObjects[i];
if (currHitObject.StackHeight != 0 && !(currHitObject is Slider))
continue;
double startTime = currHitObject.GetEndTime();
2018-10-12 15:47:18 +08:00
int sliderStack = 0;
for (int j = i + 1; j < beatmap.HitObjects.Count; j++)
{
double stackThreshold = beatmap.HitObjects[i].TimePreempt * beatmap.BeatmapInfo.StackLeniency;
if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime)
break;
// The start position of the hitobject, or the position at the end of the path if the hitobject is a slider
Vector2 position2 = currHitObject is Slider currSlider
? currSlider.Position + currSlider.Path.PositionAt(1)
: currHitObject.Position;
// Note the use of `StartTime` in the code below doesn't match stable's use of `EndTime`.
// This is because in the stable implementation, `UpdateCalculations` is not called on the inner-loop hitobject (j)
// and therefore it does not have a correct `EndTime`, but instead the default of `EndTime = StartTime`.
//
// Effects of this can be seen on https://osu.ppy.sh/beatmapsets/243#osu/1146 at sliders around 86647 ms, where
// if we use `EndTime` here it would result in unexpected stacking.
2018-10-12 15:47:18 +08:00
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
{
currHitObject.StackHeight++;
startTime = beatmap.HitObjects[j].StartTime;
2018-10-12 15:47:18 +08:00
}
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
2018-10-12 15:47:18 +08:00
{
2020-05-05 09:31:11 +08:00
// Case for sliders - bump notes down and right, rather than up and left.
2018-10-12 15:47:18 +08:00
sliderStack++;
beatmap.HitObjects[j].StackHeight -= sliderStack;
startTime = beatmap.HitObjects[j].StartTime;
2018-10-12 15:47:18 +08:00
}
}
}
}
}
}