protected double CalculateDifficulty(DifficultyType type) { var actualStrainStep = STRAIN_STEP * TimeRate; // Find the highest strain value within each strain step var highestStrains = new List <double>(); var intervalEndTime = actualStrainStep; double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval RpHitObjectDifficulty previousHitObject = null; foreach (var hitObject in DifficultyHitObjects) { // While we are beyond the current interval push the currently available maximum to our strain list while (hitObject.BaseHitObject.StartTime > intervalEndTime) { highestStrains.Add(maximumStrain); // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay // until the beginning of the next interval. if (previousHitObject == null) { maximumStrain = 0; } else { var decay = Math.Pow(RpHitObjectDifficulty.DECAY_BASE[(int)type], (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); maximumStrain = previousHitObject.Strains[(int)type] * decay; } // Go to the next time interval intervalEndTime += actualStrainStep; } // Obtain maximum strain maximumStrain = Math.Max(hitObject.Strains[(int)type], maximumStrain); previousHitObject = hitObject; } // Build the weighted sum over the highest strains for each interval double difficulty = 0; double weight = 1; highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. foreach (var strain in highestStrains) { difficulty += weight * strain; weight *= DECAY_WEIGHT; } return(difficulty); }
private void calculateSpecificStrain(RpHitObjectDifficulty previousHitObject, RpDifficultyCalculator.DifficultyType type, double timeRate) { double addition = 0; var timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; var decay = Math.Pow(DECAY_BASE[(int)type], timeElapsed / 1000); if (BaseHitObject.ObjectType == ObjectType.ContainerGroup) { // Do nothing for spinners } else if (BaseHitObject.ObjectType == ObjectType.Hold) { switch (type) { case RpDifficultyCalculator.DifficultyType.Speed: // For speed strain we treat the whole slider as a single spacing entity, since "Speed" is about how hard it is to click buttons fast. // The spacing weight exists to differentiate between being able to easily alternate or having to single. addition = spacingWeight(previousHitObject.lazySliderLength + DistanceTo(previousHitObject), type) * spacing_weight_scaling[(int)type]; break; case RpDifficultyCalculator.DifficultyType.Aim: // For Aim strain we treat each slider segment and the jump after the end of the slider as separate jumps, since movement-wise there is no difference // to multiple jumps. addition = ( spacingWeight(previousHitObject.lazySliderLength, type) + spacingWeight(DistanceTo(previousHitObject), type) ) * spacing_weight_scaling[(int)type]; break; } } else if (BaseHitObject.ObjectType == ObjectType.Hit) { addition = spacingWeight(DistanceTo(previousHitObject), type) * spacing_weight_scaling[(int)type]; } // Scale addition by the time, that elapsed. Filter out HitObjects that are too close to be played anyway to avoid crazy values by division through close to zero. // You will never find maps that require this amongst ranked maps. addition /= Math.Max(timeElapsed, 50); Strains[(int)type] = previousHitObject.Strains[(int)type] * decay + addition; }
internal double DistanceTo(RpHitObjectDifficulty other) { // Scale the distance by circle size. return((startPosition - other.endPosition).Length * scalingFactor); }
internal void CalculateStrains(RpHitObjectDifficulty previousHitObject, double timeRate) { calculateSpecificStrain(previousHitObject, RpDifficultyCalculator.DifficultyType.Speed, timeRate); calculateSpecificStrain(previousHitObject, RpDifficultyCalculator.DifficultyType.Aim, timeRate); }