static ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue
 {
     StartTime = obj.StartTime,
     EndTime   = obj.GetEndTime(),
     X         = obj.StackedPosition.X,
     Y         = obj.StackedPosition.Y
 };
示例#2
0
        /// <summary>
        /// Computes the fade time of follow point positioned between two hitobjects.
        /// </summary>
        /// <param name="start">The first <see cref="OsuHitObject"/>, where follow points should originate from.</param>
        /// <param name="end">The second <see cref="OsuHitObject"/>, which follow points should target.</param>
        /// <param name="fraction">The fractional distance along <paramref name="start"/> and <paramref name="end"/> at which the follow point is to be located.</param>
        /// <param name="fadeInTime">The fade-in time of the follow point/</param>
        /// <param name="fadeOutTime">The fade-out time of the follow point.</param>
        public static void GetFadeTimes(OsuHitObject start, OsuHitObject end, float fraction, out double fadeInTime, out double fadeOutTime)
        {
            double startTime = start.GetEndTime();
            double duration  = end.StartTime - startTime;

            fadeOutTime = startTime + fraction * duration;
            fadeInTime  = fadeOutTime - PREEMPT;
        }
示例#3
0
        private void refreshPoints()
        {
            ClearInternal(false);

            var entry = Entry;

            if (entry?.End == null)
            {
                return;
            }

            OsuHitObject start = entry.Start;
            OsuHitObject end   = entry.End;

            double startTime = start.GetEndTime();

            Vector2 startPosition = start.StackedEndPosition;
            Vector2 endPosition   = end.StackedPosition;

            Vector2 distanceVector = endPosition - startPosition;
            int     distance       = (int)distanceVector.Length;
            float   rotation       = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));

            double finalTransformEndTime = startTime;

            for (int d = (int)(SPACING * 1.5); d < distance - SPACING; d += SPACING)
            {
                float   fraction           = (float)d / distance;
                Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
                Vector2 pointEndPosition   = startPosition + fraction * distanceVector;

                GetFadeTimes(start, end, (float)d / distance, out double fadeInTime, out double fadeOutTime);

                FollowPoint fp;

                AddInternal(fp = Pool.Get());

                fp.ClearTransforms();
                fp.Position = pointStartPosition;
                fp.Rotation = rotation;
                fp.Alpha    = 0;
                fp.Scale    = new Vector2(1.5f * end.Scale);

                fp.AnimationStartTime.Value = fadeInTime;

                using (fp.BeginAbsoluteSequence(fadeInTime))
                {
                    fp.FadeIn(end.TimeFadeIn);
                    fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out);
                    fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out);
                    fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn).Expire();

                    finalTransformEndTime = fp.LifetimeEnd;
                }
            }

            entry.LifetimeEnd = finalTransformEndTime;
        }
示例#4
0
        private void refreshPoints()
        {
            ClearInternal(false);

            OsuHitObject start = Entry.Start;
            OsuHitObject end   = Entry.End;

            double startTime = start.GetEndTime();

            Vector2 startPosition = start.StackedEndPosition;
            Vector2 endPosition   = end.StackedPosition;

            Vector2 distanceVector = endPosition - startPosition;
            int     distance       = (int)distanceVector.Length;
            float   rotation       = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));

            double finalTransformEndTime = startTime;

            for (int d = (int)(SPACING * 1.5); d < distance - SPACING; d += SPACING)
            {
                float   fraction           = (float)d / distance;
                Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
                Vector2 pointEndPosition   = startPosition + fraction * distanceVector;

                GetFadeTimes(start, end, (float)d / distance, out var fadeInTime, out var fadeOutTime);

                FollowPoint fp;

                AddInternal(fp = Pool.Get());

                fp.ClearTransforms();
                fp.Position = pointStartPosition;
                fp.Rotation = rotation;
                fp.Alpha    = 0;
                fp.Scale    = new Vector2(1.5f * end.Scale);

                fp.AnimationStartTime.Value = fadeInTime;

                using (fp.BeginAbsoluteSequence(fadeInTime))
                {
                    fp.FadeIn(end.TimeFadeIn);
                    fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out);
                    fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out);
                    fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn);

                    finalTransformEndTime = fadeOutTime + end.TimeFadeIn;
                }
            }

            // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
            Entry.LifetimeEnd = finalTransformEndTime;
        }
示例#5
0
        public static void GetFadeTimes(OsuHitObject start, OsuHitObject end, float fraction, out double fadeInTime, out double fadeOutTime)
        {
            double startTime = start.GetEndTime();
            double duration  = end.StartTime - startTime;

            // Preempt time can go below 800ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
            // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear preempt function (see: OsuHitObject).
            // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
            double preempt = PREEMPT * Math.Min(1, start.TimePreempt / OsuHitObject.PREEMPT_MIN);

            fadeOutTime = startTime + fraction * duration;
            fadeInTime  = fadeOutTime - preempt;
        }
示例#6
0
        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();
                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;

                    if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
                    {
                        currHitObject.StackHeight++;
                        startTime = beatmap.HitObjects[j].GetEndTime();
                    }
                    else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
                    {
                        //Case for sliders - bump notes down and right, rather than up and left.
                        sliderStack++;
                        beatmap.HitObjects[j].StackHeight -= sliderStack;
                        startTime = beatmap.HitObjects[j].GetEndTime();
                    }
                }
            }
        }
示例#7
0
        private void addDelayedMovements(OsuHitObject h, OsuHitObject prev)
        {
            double endTime = prev.GetEndTime();

            HitWindows hitWindows = null;

            switch (h)
            {
            case HitCircle hitCircle:
                hitWindows = hitCircle.HitWindows;
                break;

            case Slider slider:
                hitWindows = slider.TailCircle.HitWindows;
                break;

            case Spinner _:
                hitWindows = defaultHitWindows;
                break;
            }

            Debug.Assert(hitWindows != null);

            // Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
            if (h.StartTime - hitWindows.WindowFor(HitResult.Miss) > endTime + hitWindows.WindowFor(HitResult.Meh) + 50)
            {
                if (!(prev is Spinner) && h.StartTime - endTime < 1000)
                {
                    AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
                }

                if (!(h is Spinner))
                {
                    AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Miss), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
                }
            }
            else if (h.StartTime - hitWindows.WindowFor(HitResult.Meh) > endTime + hitWindows.WindowFor(HitResult.Meh) + 50)
            {
                if (!(prev is Spinner) && h.StartTime - endTime < 1000)
                {
                    AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
                }

                if (!(h is Spinner))
                {
                    AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
                }
            }
            else if (h.StartTime - hitWindows.WindowFor(HitResult.Good) > endTime + hitWindows.WindowFor(HitResult.Good) + 50)
            {
                if (!(prev is Spinner) && h.StartTime - endTime < 1000)
                {
                    AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
                }

                if (!(h is Spinner))
                {
                    AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
                }
            }
        }
示例#8
0
        // Add frames to click the hitobject
        private void addHitObjectClickFrames(OsuHitObject h, Vector2 startPosition, float spinnerDirection)
        {
            // Time to insert the first frame which clicks the object
            // Here we mainly need to determine which button to use
            var action = buttonIndex % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton;

            var startFrame = new OsuReplayFrame(h.StartTime, new Vector2(startPosition.X, startPosition.Y), action);

            // TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime.
            double hEndTime = h.GetEndTime() + KEY_UP_DELAY;
            int    endDelay = h is Spinner ? 1 : 0;
            var    endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y));

            // Decrement because we want the previous frame, not the next one
            int index = FindInsertionIndex(startFrame) - 1;

            // If the previous frame has a button pressed, force alternation.
            // If there are frames ahead, modify those to use the new button press.
            // Do we have a previous frame? No need to check for < replay.Count since we decremented!
            if (index >= 0)
            {
                var previousFrame   = (OsuReplayFrame)Frames[index];
                var previousActions = previousFrame.Actions;

                // If a button is already held, then we simply alternate
                if (previousActions.Any())
                {
                    // Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button.
                    if (previousActions.Contains(action))
                    {
                        action = action == OsuAction.LeftButton ? OsuAction.RightButton : OsuAction.LeftButton;
                        startFrame.Actions.Clear();
                        startFrame.Actions.Add(action);
                    }

                    // We always follow the most recent slider / spinner, so remove any other frames that occur while it exists.
                    int endIndex = FindInsertionIndex(endFrame);

                    if (index < Frames.Count - 1)
                    {
                        Frames.RemoveRange(index + 1, Math.Max(0, endIndex - (index + 1)));
                    }

                    // After alternating we need to keep holding the other button in the future rather than the previous one.
                    for (int j = index + 1; j < Frames.Count; ++j)
                    {
                        var frame = (OsuReplayFrame)Frames[j];

                        // Don't affect frames which stop pressing a button!
                        if (j < Frames.Count - 1 || frame.Actions.SequenceEqual(previousActions))
                        {
                            frame.Actions.Clear();
                            frame.Actions.Add(action);
                        }
                    }
                }
            }

            AddFrameToReplay(startFrame);

            switch (h)
            {
            // We add intermediate frames for spinning / following a slider here.
            case Spinner spinner:
                Vector2 difference = startPosition - SPINNER_CENTRE;

                float radius = difference.Length;
                float angle  = radius == 0 ? 0 : MathF.Atan2(difference.Y, difference.X);

                double t;

                for (double j = h.StartTime + FrameDelay; j < spinner.EndTime; j += FrameDelay)
                {
                    t = ApplyModsToTime(j - h.StartTime) * spinnerDirection;

                    Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);
                    AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action));
                }

                t = ApplyModsToTime(spinner.EndTime - h.StartTime) * spinnerDirection;
                Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);

                AddFrameToReplay(new OsuReplayFrame(spinner.EndTime, new Vector2(endPosition.X, endPosition.Y), action));

                endFrame.Position = endPosition;
                break;

            case Slider slider:
                for (double j = FrameDelay; j < slider.Duration; j += FrameDelay)
                {
                    Vector2 pos = slider.StackedPositionAt(j / slider.Duration);
                    AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action));
                }

                AddFrameToReplay(new OsuReplayFrame(slider.EndTime, new Vector2(slider.StackedEndPosition.X, slider.StackedEndPosition.Y), action));
                break;
            }

            // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!
            if (Frames[Frames.Count - 1].Time <= endFrame.Time)
            {
                AddFrameToReplay(endFrame);
            }
        }
示例#9
0
        private void applyStacking(Beatmap <OsuHitObject> beatmap, int startIndex, int endIndex)
        {
            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.");
            }

            int extendedEndIndex = endIndex;

            if (endIndex < beatmap.HitObjects.Count - 1)
            {
                // Extend the end index to include objects they are stacked on
                for (int i = endIndex; i >= startIndex; i--)
                {
                    int stackBaseIndex = i;

                    for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++)
                    {
                        OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex];
                        if (stackBaseObject is Spinner)
                        {
                            break;
                        }

                        OsuHitObject objectN = beatmap.HitObjects[n];
                        if (objectN is Spinner)
                        {
                            continue;
                        }

                        double endTime        = stackBaseObject.GetEndTime();
                        double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;

                        if (objectN.StartTime - endTime > stackThreshold)
                        {
                            //We are no longer within stacking range of the next object.
                            break;
                        }

                        if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance ||
                            (stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance))
                        {
                            stackBaseIndex = n;

                            // HitObjects after the specified update range haven't been reset yet
                            objectN.StackHeight = 0;
                        }
                    }

                    if (stackBaseIndex > extendedEndIndex)
                    {
                        extendedEndIndex = stackBaseIndex;
                        if (extendedEndIndex == beatmap.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 = beatmap.HitObjects[i];
                if (objectI.StackHeight != 0 || objectI is Spinner)
                {
                    continue;
                }

                double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo.StackLeniency;

                /* 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 = beatmap.HitObjects[n];
                        if (objectN is Spinner)
                        {
                            continue;
                        }

                        double endTime = objectN.GetEndTime();

                        if (objectI.StartTime - 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 && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance)
                        {
                            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 = beatmap.HitObjects[j];
                                if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance)
                                {
                                    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 (Vector2Extensions.Distance(objectN.Position, objectI.Position) < stack_distance)
                        {
                            //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 = beatmap.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 (Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance)
                        {
                            objectN.StackHeight = objectI.StackHeight + 1;
                            objectI             = objectN;
                        }
                    }
                }
            }
        }
示例#10
0
 public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
     : base(hitObject, hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime - 1)
 {
     Masking = true;
 }