Example #1
0
        /// <summary>
        /// Calculates the movement time, effective distance and other details for the movement from objPrev to objCurr.
        /// </summary>
        /// <param name="fourthLastObject">Hit object four objects ago, relative to <paramref name="currentObject"/>.</param>
        /// <param name="secondLastObject">Hit object immediately preceding <paramref name="lastObject"/></param>
        /// <param name="lastObject">Hit object immediately preceding <paramref name="currentObject"/>.</param>
        /// <param name="currentObject">The hit object being currently considered.</param>
        /// <param name="nextObject">Hit object immediately succeeding <paramref name="currentObject"/>.</param>
        /// <param name="tapStrain">The tap strain of the current object.</param> TODO: does this have to be passed down? maybe store in the object?
        /// <param name="noteDensity">The visual note density of the current object.</param> TODO: above
        /// <param name="gameplayRate">The current rate of the gameplay clock.</param>
        /// <param name="hidden">Whether the hidden mod is active.</param>
        /// <returns>List of movements performed in attempt to hit the current object.</returns>
        public static List <OsuMovement> Extract(
            [CanBeNull] OsuHitObject secondLastObject,
            OsuHitObject lastObject,
            OsuHitObject currentObject,
            [CanBeNull] OsuHitObject nextObject,
            double tapStrain,
            double gameplayRate,
            bool hidden,
            double noteDensity,
            [CanBeNull] OsuHitObject fourthLastObject = null)
        {
            var movement   = new OsuMovement();
            var parameters = new MovementExtractionParameters(fourthLastObject, secondLastObject, lastObject, currentObject, nextObject, gameplayRate);

            movement.RawMovementTime = parameters.LastToCurrent.TimeDelta;
            movement.StartTime       = currentObject.StartTime / 1000.0;

            if (currentObject is Spinner || lastObject is Spinner)
            {
                movement.Throughput    = 0;
                movement.Distance      = 0;
                movement.MovementTime  = 1;
                movement.Cheesablility = 0;
                movement.CheeseWindow  = 0;
                return(new List <OsuMovement> {
                    movement
                });
            }

            movement.EndsOnSlider = currentObject is Slider;

            double movementThroughput = FittsLaw.Throughput(parameters.LastToCurrent.RelativeLength, parameters.LastToCurrent.TimeDelta);

            movement.Throughput = movementThroughput;
            movement.Distance   = correctMovementDistance(parameters, movementThroughput, tapStrain, hidden, noteDensity);
            calculateCheeseWindow(parameters, movementThroughput);
            movement.MovementTime  = parameters.LastToCurrent.TimeDelta;
            movement.Cheesablility = parameters.Cheesability;
            movement.CheeseWindow  = parameters.CheeseWindow;

            var movementWithNested = new List <OsuMovement> {
                movement
            };

            // add zero difficulty movements corresponding to slider ticks/slider ends so combo is reflected properly
            int extraNestedCount = currentObject.NestedHitObjects.Count - 1;

            for (int i = 0; i < extraNestedCount; i++)
            {
                movementWithNested.Add(OsuMovement.Empty(movement.StartTime));
            }

            return(movementWithNested);
        }
Example #2
0
        /// <summary>
        /// Correction #5 - Cheesing
        /// The player might make the movement from previous object to current easier by hitting former early and latter late.
        /// Here we estimate the amount of such cheesing to update MovementTime accordingly.
        /// </summary>
        private static void calculateCheeseWindow(MovementExtractionParameters p, double movementThroughput)
        {
            double timeEarly         = 0;
            double timeLate          = 0;
            double cheesabilityEarly = 0;
            double cheesabilityLate  = 0;

            if (p.LastToCurrent.RelativeLength > 0)
            {
                double secondLastToLastReciprocalMovementLength;
                double secondLastToLastThroughput;

                if (p.SecondLastObject != null)
                {
                    Debug.Assert(p.SecondLastToLast != null);

                    secondLastToLastReciprocalMovementLength = 1 / (p.SecondLastToLast.Value.TimeDelta + 1e-10);
                    secondLastToLastThroughput = FittsLaw.Throughput(p.SecondLastToLast.Value.RelativeLength, p.SecondLastToLast.Value.TimeDelta);
                }
                else
                {
                    secondLastToLastReciprocalMovementLength = 0;
                    secondLastToLastThroughput = 0;
                }

                cheesabilityEarly = SpecialFunctions.Logistic((secondLastToLastThroughput / movementThroughput - 0.6) * (-15)) * 0.5;
                timeEarly         = cheesabilityEarly * (1 / (1 / (p.LastToCurrent.TimeDelta + 0.07) + secondLastToLastReciprocalMovementLength));

                double currentToNextReciprocalMovementLength;
                double currentToNextThroughput;

                if (p.NextObject != null)
                {
                    Debug.Assert(p.CurrentToNext != null);

                    currentToNextReciprocalMovementLength = 1 / (p.CurrentToNext.Value.TimeDelta + 1e-10);
                    currentToNextThroughput = FittsLaw.Throughput(p.CurrentToNext.Value.RelativeLength, p.CurrentToNext.Value.TimeDelta);
                }
                else
                {
                    currentToNextReciprocalMovementLength = 0;
                    currentToNextThroughput = 0;
                }

                cheesabilityLate = SpecialFunctions.Logistic((currentToNextThroughput / movementThroughput - 0.6) * (-15)) * 0.5;
                timeLate         = cheesabilityLate * (1 / (1 / (p.LastToCurrent.TimeDelta + 0.07) + currentToNextReciprocalMovementLength));
            }

            p.Cheesability = cheesabilityEarly + cheesabilityLate;
            p.CheeseWindow = (timeEarly + timeLate) / (p.LastToCurrent.TimeDelta + 1e-10);
        }