private void CalculateSpecificStrain(tpHitObject PreviousHitObject, AiModtpDifficulty.DifficultyType Type) { double Addition = 0; double TimeElapsed = BaseHitObject.StartTime - PreviousHitObject.BaseHitObject.StartTime; double Decay = Math.Pow(DECAY_BASE[(int)Type], TimeElapsed / 1000); if ((BaseHitObject.Type & HitObjectType.Spinner) > 0) { // Do nothing for spinners } else if ((BaseHitObject.Type & HitObjectType.Slider) > 0) { switch(Type) { case AiModtpDifficulty.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.LazySliderLengthFirst + PreviousHitObject.LazySliderLengthSubsequent * (PreviousHitObject.BaseHitObject.SegmentCount - 1) + DistanceTo(PreviousHitObject), Type) * SPACING_WEIGHT_SCALING[(int)Type]; break; case AiModtpDifficulty.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.LazySliderLengthFirst, Type) + SpacingWeight(PreviousHitObject.LazySliderLengthSubsequent, Type) * (PreviousHitObject.BaseHitObject.SegmentCount - 1) + SpacingWeight(DistanceTo(PreviousHitObject), Type) ) * SPACING_WEIGHT_SCALING[(int)Type]; break; } } else if ((BaseHitObject.Type & HitObjectType.Normal) > 0) { 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; }
// Caution: The subjective values are strong with this one private static double SpacingWeight(double distance, AiModtpDifficulty.DifficultyType Type) { switch(Type) { case AiModtpDifficulty.DifficultyType.Speed: { double Weight; if (distance > SINGLE_SPACING_TRESHOLD) { Weight = 2.5; } else if (distance > STREAM_SPACING_TRESHOLD) { Weight = 1.6 + 0.9 * (distance - STREAM_SPACING_TRESHOLD) / (SINGLE_SPACING_TRESHOLD - STREAM_SPACING_TRESHOLD); } else if (distance > ALMOST_DIAMETER) { Weight = 1.2 + 0.4 * (distance - ALMOST_DIAMETER) / (STREAM_SPACING_TRESHOLD - ALMOST_DIAMETER); } else if (distance > ALMOST_DIAMETER / 2) { Weight = 0.95 + 0.25 * (distance - (ALMOST_DIAMETER / 2)) / (ALMOST_DIAMETER / 2); } else { Weight = 0.95; } return Weight; } case AiModtpDifficulty.DifficultyType.Aim: return Math.Pow(distance, 0.99); // Should never happen. default: return 0; } }