コード例 #1
0
ファイル: Aim.cs プロジェクト: tjgtll/osu
        private static string generateGraphText(List <OsuMovement> movements, double tp)
        {
            var sw = new StringWriter();

            foreach (var movement in movements)
            {
                double time        = movement.Time;
                double ipRaw       = movement.IP12;
                double ipCorrected = FittsLaw.CalculateIP(movement.D, movement.MT * (1 + defaultCheeseLevel * movement.CheesableRatio));
                double missProb    = 1 - calculateCheeseHitProb(movement, tp, defaultCheeseLevel);

                sw.WriteLine($"{time} {ipRaw} {ipCorrected} {missProb}");
            }

            string graphText = sw.ToString();

            sw.Dispose();
            return(graphText);
        }
コード例 #2
0
ファイル: OsuMovement.cs プロジェクト: tjgtll/osu
        public static List <OsuMovement> ExtractMovement(OsuHitObject obj0, OsuHitObject obj1, OsuHitObject obj2, OsuHitObject obj3,
                                                         Vector <double> tapStrain, double clockRate)
        {
            var movement = new OsuMovement();

            double t12 = (obj2.StartTime - obj1.StartTime) / clockRate / 1000.0;

            movement.RawMT = t12;
            movement.Time  = obj2.StartTime / 1000.0;

            if (obj2 is Spinner || obj1 is Spinner)
            {
                movement.IP12           = 0;
                movement.D              = 0;
                movement.MT             = 1;
                movement.Cheesablility  = 0;
                movement.CheesableRatio = 0;
                return(new List <OsuMovement>()
                {
                    movement
                });
            }

            if (obj0 is Spinner)
            {
                obj0 = null;
            }

            if (obj3 is Spinner)
            {
                obj3 = null;
            }

            var pos1 = Vector <double> .Build.Dense(new[] { (double)obj1.Position.X, (double)obj1.Position.Y });

            var pos2 = Vector <double> .Build.Dense(new[] { (double)obj2.Position.X, (double)obj2.Position.Y });

            var    s12  = (pos2 - pos1) / (2 * obj2.Radius);
            double d12  = s12.L2Norm();
            double IP12 = FittsLaw.CalculateIP(d12, t12);

            movement.IP12 = IP12;

            var s01 = Vector <double> .Build.Dense(2);

            var s23 = Vector <double> .Build.Dense(2);

            double d01 = 0;
            double d23 = 0;
            double t01 = 0;
            double t23 = 0;

            double flowiness012    = 0;
            double flowiness123    = 0;
            bool   obj1InTheMiddle = false;
            bool   obj2InTheMiddle = false;


            // Correction #1 - The Previous Object
            // Estimate how obj0 affects the difficulty of hitting obj2
            double correction0 = 0;

            if (obj0 != null)
            {
                var pos0 = Vector <double> .Build.Dense(new[] { (double)obj0.Position.X, (double)obj0.Position.Y });

                s01 = (pos1 - pos0) / (2 * obj2.Radius);
                d01 = s01.L2Norm();
                t01 = (obj1.StartTime - obj0.StartTime) / clockRate / 1000.0;

                if (d12 != 0)
                {
                    double tRatio0 = t12 / t01;

                    if (tRatio0 > tRatioThreshold)
                    {
                        if (d01 == 0)
                        {
                            correction0 = correction0Still;
                        }
                        else
                        {
                            double cos012             = Math.Min(Math.Max(-s01.DotProduct(s12) / d01 / d12, -1), 1);
                            double correction0_moving = correction0MovingSpline.Interpolate(cos012);

                            double movingness = SpecialFunctions.Logistic(d01 * 2) * 2 - 1;
                            correction0 = (movingness * correction0_moving + (1 - movingness) * correction0Still) * 1.5;
                        }
                    }
                    else if (tRatio0 < 1 / tRatioThreshold)
                    {
                        if (d01 == 0)
                        {
                            correction0 = 0;
                        }
                        else
                        {
                            double cos012 = Math.Min(Math.Max(-s01.DotProduct(s12) / d01 / d12, -1), 1);
                            correction0 = (1 - cos012) * SpecialFunctions.Logistic((d01 * tRatio0 - 1.5) * 4) * 0.3;
                        }
                    }
                    else
                    {
                        obj1InTheMiddle = true;

                        var    normalized_pos0 = -s01 / t01 * t12;
                        double x0 = normalized_pos0.DotProduct(s12) / d12;
                        double y0 = (normalized_pos0 - x0 * s12 / d12).L2Norm();

                        double correction0Flow = calcCorrection0Or3(d12, x0, y0, k0fInterp, scale0fInterp, coeffs0fInterps);
                        double correction0Snap = calcCorrection0Or3(d12, x0, y0, k0sInterp, scale0sInterp, coeffs0sInterps);
                        double correction0Stop = calcCorrection0Stop(d12, x0, y0);

                        flowiness012 = SpecialFunctions.Logistic((correction0Snap - correction0Flow - 0.05) * 20);

                        correction0 = Mean.PowerMean(new double[] { correction0Flow, correction0Snap, correction0Stop }, -10) * 1.3;

                        //Console.Write(obj2.StartTime + " ");
                        //Console.Write(correction0Flow.ToString("N3") + " ");
                        //Console.Write(correction0Snap.ToString("N3") + " ");
                        //Console.Write(correction0Stop.ToString("N3") + " ");
                        //Console.Write(correction0.ToString("N3") + " ");
                        //Console.WriteLine();
                    }
                }
            }

            // Correction #2 - The Next Object
            // Estimate how obj3 affects the difficulty of hitting obj2
            double correction3 = 0;

            if (obj3 != null)
            {
                var pos3 = Vector <double> .Build.Dense(new[] { (double)obj3.Position.X, (double)obj3.Position.Y });

                s23 = (pos3 - pos2) / (2 * obj2.Radius);
                d23 = s23.L2Norm();
                t23 = (obj3.StartTime - obj2.StartTime) / clockRate / 1000.0;

                if (d12 != 0)
                {
                    double tRatio3 = t12 / t23;

                    if (tRatio3 > tRatioThreshold)
                    {
                        if (d23 == 0)
                        {
                            correction3 = 0;
                        }
                        else
                        {
                            double cos123             = Math.Min(Math.Max(-s12.DotProduct(s23) / d12 / d23, -1), 1);
                            double correction3_moving = correction0MovingSpline.Interpolate(cos123);

                            double movingness = SpecialFunctions.Logistic(d23 * 6 - 5) - SpecialFunctions.Logistic(-5);
                            correction3 = (movingness * correction3_moving) * 0.5;
                        }
                    }
                    else if (tRatio3 < 1 / tRatioThreshold)
                    {
                        if (d23 == 0)
                        {
                            correction3 = 0;
                        }
                        else
                        {
                            double cos123 = Math.Min(Math.Max(-s12.DotProduct(s23) / d12 / d23, -1), 1);
                            correction3 = (1 - cos123) * SpecialFunctions.Logistic((d23 * tRatio3 - 1.5) * 4) * 0.15;
                        }
                    }
                    else
                    {
                        obj2InTheMiddle = true;

                        var    normalizedPos3 = s23 / t23 * t12;
                        double x3             = normalizedPos3.DotProduct(s12) / d12;
                        double y3             = (normalizedPos3 - x3 * s12 / d12).L2Norm();

                        double correction3Flow = calcCorrection0Or3(d12, x3, y3, k3fInterp, scale3fInterp, coeffs3fInterps);
                        double correction3Snap = calcCorrection0Or3(d12, x3, y3, k3sInterp, scale3sInterp, coeffs3sInterps);

                        flowiness123 = SpecialFunctions.Logistic((correction3Snap - correction3Flow - 0.05) * 20);

                        correction3 = Math.Max(Mean.PowerMean(correction3Flow, correction3Snap, -10) - 0.1, 0) * 0.5;
                    }
                }
            }

            // Correction #3 - 4-object pattern
            // Estimate how the whole pattern consisting of obj0 to obj3 affects
            // the difficulty of hitting obj2. This only takes effect when the pattern
            // is not so spaced (i.e. does not contain jumps)
            double patternCorrection = 0;

            if (obj1InTheMiddle && obj2InTheMiddle)
            {
                double gap = (s12 - s23 / 2 - s01 / 2).L2Norm() / (d12 + 0.1);
                patternCorrection = (SpecialFunctions.Logistic((gap - 0.75) * 8) - SpecialFunctions.Logistic(-6)) *
                                    SpecialFunctions.Logistic((d01 - 0.7) * 10) * SpecialFunctions.Logistic((d23 - 0.7) * 10) *
                                    Mean.PowerMean(flowiness012, flowiness123, 2) * 0.6;
                //patternCorrection = 0;
            }

            // Correction #4 - Tap Strain
            // Estimate how tap strain affects difficulty
            double tapCorrection = 0;

            if (d12 > 0 && tapStrain != null)
            {
                tapCorrection = SpecialFunctions.Logistic((Mean.PowerMean(tapStrain, 2) / IP12 - 1.34) / 0.1) * 0.25;
            }

            // Correction #5 - Cheesing
            // The player might make the movement of obj1 -> obj2 easier by
            // hitting obj1 early and obj2 late. Here we estimate the amount of
            // cheesing and update MT accordingly.
            double timeEarly         = 0;
            double timeLate          = 0;
            double cheesabilityEarly = 0;
            double cheesabilityLate  = 0;

            if (d12 > 0)
            {
                double t01Reciprocal;
                double ip01;
                if (obj0 != null)
                {
                    t01Reciprocal = 1 / (t01 + 1e-10);
                    ip01          = FittsLaw.CalculateIP(d01, t01);
                }
                else
                {
                    t01Reciprocal = 0;
                    ip01          = 0;
                }
                cheesabilityEarly = SpecialFunctions.Logistic((ip01 / IP12 - 0.6) * (-15)) * 0.5;
                timeEarly         = cheesabilityEarly * (1 / (1 / (t12 + 0.07) + t01Reciprocal));

                double t23Reciprocal;
                double ip23;
                if (obj3 != null)
                {
                    t23Reciprocal = 1 / (t23 + 1e-10);
                    ip23          = FittsLaw.CalculateIP(d23, t23);
                }
                else
                {
                    t23Reciprocal = 0;
                    ip23          = 0;
                }
                cheesabilityLate = SpecialFunctions.Logistic((ip23 / IP12 - 0.6) * (-15)) * 0.5;
                timeLate         = cheesabilityLate * (1 / (1 / (t12 + 0.07) + t23Reciprocal));
            }

            // Correction #6 - Small circle bonus
            double smallCircleBonus = SpecialFunctions.Logistic((55 - 2 * obj2.Radius) / 3.0) * 0.3;

            // Correction #7 - Stacked notes nerf
            double stackedThreshold = 0.8;
            double d12StackedNerf;

            if (d12 < stackedThreshold)
            {
                d12StackedNerf = Math.Max(1.4 * (d12 - stackedThreshold) + stackedThreshold, 0);
            }
            else
            {
                d12StackedNerf = d12;
            }

            // Apply the corrections
            double d12WithCorrection = d12StackedNerf * (1 + smallCircleBonus) * (1 + correction0 + correction3 + patternCorrection) *
                                       (1 + tapCorrection);

            movement.D              = d12WithCorrection;
            movement.MT             = t12;
            movement.Cheesablility  = cheesabilityEarly + cheesabilityLate;
            movement.CheesableRatio = (timeEarly + timeLate) / (t12 + 1e-10);

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

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

            for (int i = 0; i < extraNestedCount; i++)
            {
                movementWithNested.Add(GetEmptyMovement(movement.Time));
            }

            return(movementWithNested);
        }