/// <summary> /// Calculates the strain for one difficulty type and stores it in <paramref name="obj"/>. This assumes that /// <see cref="HitObject.Normpos"/> is already computed. This also sets <see cref="HitObject.IsSingle"/> if /// <paramref name="type"/> is <seealso cref="StrainType.Speed"/>. /// </summary> private static void DiffStrain(StrainType type, HitObject obj, HitObject prev, double speedMul) { double value = 0.0; double timeElapsed = (obj.Time - prev.Time) / speedMul; double decay = Math.Pow(DecayBase[type], timeElapsed / 1000.0); obj.DeltaTime = timeElapsed; /* this implementation doesn't account for sliders */ if ((obj.Type & (HitObjectType.Slider | HitObjectType.Circle)) != 0) { double distance = (obj.Normpos - prev.Normpos).Length; obj.DeltaDistance = distance; if (type == StrainType.Speed) { obj.IsSingle = distance > SingleSpacing; } value = DiffSpacingWeight(type, distance, timeElapsed, prev.DeltaDistance, prev.DeltaTime, obj.Angle); value *= WeightScaling[type]; } obj.Strains[type] = prev.Strains[type] * decay + value; }
private static double DiffSpacingWeight(StrainType type, double distance) { switch (type) { case StrainType.Aim: return(Math.Pow(distance, 0.99)); case StrainType.Speed: if (distance > SingleSpacing) { return(2.5); } else if (distance > StreamSpacing) { return(1.6 + 0.9 * (distance - StreamSpacing) / (SingleSpacing - StreamSpacing)); } else if (distance > AlmostDiameter) { return(1.2 + 0.4 * (distance - AlmostDiameter) / (StreamSpacing - AlmostDiameter)); } else if (distance > AlmostDiameter / 2.0) { return(0.95 + 0.25 * (distance - AlmostDiameter / 2.0) / (AlmostDiameter / 2.0)); } return(0.95); default: throw new InvalidOperationException("this difficulty type does not exist"); } }
private double CalcIndividual(StrainType type) { strains.Clear(); double strainStep = StrainStep * speedMul; double intervalEnd = strainStep; double maxStrain = 0.0; //calculate all strains for (int i = 0; i < Beatmap.Objects.Count; ++i) { HitObject obj = Beatmap.Objects[i]; HitObject prev = i > 0 ? Beatmap.Objects[i - 1] : null; if (prev != null) { DiffStrain(type, obj, prev, speedMul); } while (obj.Time > intervalEnd) { //add max strain for this interval strains.Add(maxStrain); if (prev != null) { //decay last object's strains until the next interval and use that as the initial max strain double decay = Math.Pow(DecayBase[type], (intervalEnd - prev.Time) / 1000.0); maxStrain = prev.Strains[type] * decay; } else { maxStrain = 0.0; } intervalEnd += strainStep; } maxStrain = Math.Max(maxStrain, obj.Strains[type]); } //weigh the top strains sorted from highest to lowest double weight = 1.0; double difficulty = 0.0; strains.Sort(); strains.Reverse(); foreach (double strain in strains) { difficulty += strain * weight; weight *= DecayWeight; } return(difficulty); }
private static double DiffSpacingWeight(StrainType type, double distance, double deltaTime, double prevDistance, double prevDeltaTime, double angle) { double angleBonus; double strainTime = Math.Max(deltaTime, 50.0f); switch (type) { case StrainType.Aim: { double result = 0; double prevStrainTime = Math.Max(prevDeltaTime, 50.0f); if (!(Double.IsNaN(angle) || Double.IsInfinity(angle)) && angle > AimAngleBonusBegin) { angleBonus = Math.Sqrt( Math.Max(prevDistance - AngleBonusScale, 0) * Math.Pow(Math.Sin(angle - AimAngleBonusBegin), 2) * Math.Max(distance - AngleBonusScale, 0) ); result = 1.5f * Math.Pow(Math.Max(0, angleBonus), 0.99) / Math.Max(AimTimingThreshold, prevStrainTime); } var weightedDistance = Math.Pow(distance, 0.99); return(Math.Max( result + weightedDistance / Math.Max(AimTimingThreshold, strainTime), weightedDistance / strainTime )); } case StrainType.Speed: { distance = Math.Min(distance, SingleSpacing); deltaTime = Math.Max(deltaTime, MaxSpeedBonus); double speedBonus = 1.0f; if (deltaTime < MinSpeedBonus) { speedBonus += Math.Pow((MinSpeedBonus - deltaTime) / 40.0f, 2); } angleBonus = 1.0f; if (!(Double.IsNaN(angle) || Double.IsInfinity(angle)) && angle < SpeedAngleBonusBegin) { double s = Math.Sin(1.5 * (SpeedAngleBonusBegin - angle)); angleBonus += Math.Pow(s, 2) / 3.57f; if (angle < Math.PI / 2) { angleBonus = 1.28f; if (distance < AngleBonusScale && angle < Math.PI / 4) { angleBonus += (1 - angleBonus) * Math.Min((AngleBonusScale - distance) / 10, 1); } else if (distance < AngleBonusScale) { angleBonus += (1 - angleBonus) * Math.Min((AngleBonusScale - distance) / 10, 1) * Math.Sin((Math.PI / 2 - angle) * 4 / Math.PI); } } } return(( (1 + (speedBonus - 1) * 0.75f) * angleBonus * (0.95f + speedBonus * Math.Pow(distance / SingleSpacing, 3.5)) ) / strainTime); } default: throw new InvalidOperationException("this difficulty type does not exist"); } }