protected override void RunAllRules(List<HitObjectBase> hitObjects)
{
BeatmapBase Beatmap = OsuHelper.GetCurrentBeatmap();
// Mods are not yet supported. TODO
// Fill our custom tpHitObject class, that carries additional information
tpHitObjects = new List<tpHitObject>(hitObjects.Count);
float CircleRadius = (PLAYFIELD_WIDTH / 16.0f) * (1.0f - 0.7f * ((float)Beatmap.DifficultyCircleSize - 5.0f) / 5.0f);
foreach(HitObjectBase hitObject in hitObjects)
{
tpHitObjects.Add(new tpHitObject(hitObject, CircleRadius));
}
// Sort tpHitObjects by StartTime of the HitObjects - just to make sure. Not using CompareTo, since it results in a crash (HitObjectBase inherits MarshalByRefObject)
tpHitObjects.Sort((a,b) => a.BaseHitObject.StartTime - b.BaseHitObject.StartTime);
if (CalculateStrainValues() == false)
{
Reports.Add(new AiReport(Severity.Error, "Could not compute strain values. Aborting difficulty calculation."));
return;
}
double SpeedDifficulty = CalculateDifficulty(DifficultyType.Speed);
double AimDifficulty = CalculateDifficulty(DifficultyType.Aim);
// OverallDifficulty is not considered in this algorithm and neither is HpDrainRate. That means, that in this form the algorithm determines how hard it physically is
// to play the map, assuming, that too much of an error will not lead to a death.
// It might be desirable to include OverallDifficulty into map difficulty, but in my personal opinion it belongs more to the weighting of the actual peformance
// and is superfluous in the beatmap difficulty rating.
// If it were to be considered, then I would look at the hit window of normal HitCircles only, since Sliders and Spinners are (almost) "free" 300s and take map length
// into account as well.
Reports.Add(new AiReport(Severity.Info, "Speed difficulty: " + SpeedDifficulty + " | Aim difficulty: " + AimDifficulty));
// The difficulty can be scaled by any desired metric.
// In osu!tp it gets squared to account for the rapid increase in difficulty as the limit of a human is approached. (Of course it also gets scaled afterwards.)
// It would not be suitable for a star rating, therefore:
// The following is a proposal to forge a star rating from 0 to 5. It consists of taking the square root of the difficulty, since by simply scaling the easier
// 5-star maps would end up with one star.
double SpeedStars = Math.Sqrt(SpeedDifficulty) * STAR_SCALING_FACTOR;
double AimStars = Math.Sqrt(AimDifficulty) * STAR_SCALING_FACTOR;
Reports.Add(new AiReport(Severity.Info, "Speed stars: " + SpeedStars + " | Aim stars: " + AimStars));
// Again, from own observations and from the general opinion of the community a map with high speed and low aim (or vice versa) difficulty is harder,
// than a map with mediocre difficulty in both. Therefore we can not just add both difficulties together, but will introduce a scaling that favors extremes.
double StarRating = SpeedStars + AimStars + Math.Abs(SpeedStars - AimStars) * EXTREME_SCALING_FACTOR;
// Another approach to this would be taking Speed and Aim separately to a chosen power, which again would be equivalent. This would be more convenient if
// the hit window size is to be considered as well.
// Note: The star rating is tuned extremely tight! Airman (/b/104229) and Freedom Dive (/b/126645), two of the hardest ranked maps, both score ~4.66 stars.
// Expect the easier kind of maps that officially get 5 stars to obtain around 2 by this metric. The tutorial still scores about half a star.
// Tune by yourself as you please. ;)
Reports.Add(new AiReport(Severity.Info, "Total star rating: " + StarRating));
}