/// <summary> /// Calculates the hp drop rate via some insane looping through the map. /// Oh god. /// </summary> /// <returns></returns> internal double CalculateHpDropRate() { double testDrop = 0.05; double lowestHpEver = hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 195, 160, 60); double lowestHpComboEnd = hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 198, 170, 80); double lowestHpEnd = hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 198, 180, 80); double HpRecoveryAvailable = hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 8, 4, 0); do { TotalHitsPossible = 0; HpBar.Reset(); ComboCounter.HitCombo = 0; Player.currentScore.TotalScore = 0; double lowestHp = HpBar.CurrentHp; int lastTime = hitObjectManager.hitObjects[0].StartTime - hitObjectManager.PreEmpt; bool fail = false; int comboTooLowCount = 0; int breakCount = player.eventManager.eventBreaks.Count; int breakNumber = 0; for (int i = 0; i < hitObjectManager.hitObjectsCount; i++) { HitObject h = hitObjectManager.hitObjects[i]; //Find active break (between current and lastTime) int localLastTime = lastTime; int breakTime = 0; if (breakCount > 0 && breakNumber < breakCount) { Event e = player.eventManager.eventBreaks[breakNumber]; if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime) { breakTime = e.Length; breakNumber++; } } HpBar.ReduceCurrentHp(testDrop * (h.StartTime - lastTime - breakTime)); lastTime = h.EndTime; if (HpBar.CurrentHp < lowestHp) { lowestHp = HpBar.CurrentHp; } if (HpBar.CurrentHp <= lowestHpEver) { //Debug.Print("Overall score drops below " + lowestHpEver + " at " + lastTime + " (" + testDrop + ", lowest " + lowestHp + ")"); fail = true; testDrop *= 0.96; break; } if (h is HitCircleFruitsTick) { IncreaseScoreHit(IncreaseScoreType.FruitTick, h); } else if (h is HitCircleFruitsTickTiny) { IncreaseScoreHit(IncreaseScoreType.FruitTickTiny, h); } else if (h is HitCircleFruitsSpin) { HpBar.IncreaseCurrentHp(HpMultiplierNormal / 2); } else { if (i == hitObjectManager.hitObjectsCount - 1 || hitObjectManager.hitObjects[i + 1].NewCombo) { IncreaseScoreHit(IncreaseScoreType.Hit300g, h); if (HpBar.CurrentHp < lowestHpComboEnd) { if (++comboTooLowCount > 2) { HpMultiplierComboEnd *= 1.07; HpMultiplierNormal *= 1.03; fail = true; break; } } } else { IncreaseScoreHit(IncreaseScoreType.Hit300, h); } } if (!(h is HitCircleFruitsTickTiny) && !(h is HitCircleFruitsSpin)) { TotalHitsPossible++; } h.MaxHp = HpBar.CurrentHp; } if (!fail && HpBar.CurrentHp < lowestHpEnd) { //Debug.Print("Song ends on " + currentHp + " - being more lenient"); fail = true; testDrop *= 0.94; HpMultiplierComboEnd *= 1.01; HpMultiplierNormal *= 1.01; } double recovery = (HpBar.CurrentHpUncapped - HP_BAR_MAXIMUM) / hitObjectManager.hitObjectsCount; if (!fail && recovery < HpRecoveryAvailable) { //Debug.Print("Song has average " + recovery + " recovery - being more lenient"); fail = true; testDrop *= 0.96; HpMultiplierComboEnd *= 1.02; HpMultiplierNormal *= 1.01; } if (fail) { continue; } if (GameBase.TestMode) { PlayerTest pt = player as PlayerTest; if (pt != null) { pt.testMaxScore = Player.currentScore.TotalScore; pt.testMaxCombo = ComboCounter.HitCombo; } } Debug.Print("Loaded Beatmap " + BeatmapManager.Current.Filename); Debug.Print(string.Empty); Debug.Print(" stars: " + BeatmapManager.Current.DifficultyEyupStars); Debug.Print(" stars: " + BeatmapManager.Current.DifficultyEchoStars); Debug.Print(" slider rate: " + BeatmapManager.Current.DifficultySliderMultiplier); Debug.Print(" playable length: " + BeatmapManager.Current.DrainLength); Debug.Print(" hitobjects: " + BeatmapManager.Current.ObjectCount); Debug.Print(" hitcircles: " + hitObjectManager.hitObjects.FindAll(h => h.IsType(HitObjectType.Normal)).Count); Debug.Print(" sliders: " + hitObjectManager.hitObjects.FindAll(h => h.IsType(HitObjectType.Slider)).Count); Debug.Print(" spinners: " + hitObjectManager.hitObjects.FindAll(h => h.IsType(HitObjectType.Spinner)).Count); Debug.Print(" drain rate: {0:00.00}%/s", (testDrop * 60) / 2); Debug.Print(" lowest hp: {0:00.00}%", lowestHp / 2); Debug.Print("normal multiplier: {0:00.00}x", HpMultiplierNormal); Debug.Print("combo multiplier: {0:00.00}x", HpMultiplierComboEnd); Debug.Print(" excess hp recov: {0:00.00}%/hitobject", (float)(HpBar.CurrentHpUncapped - HP_BAR_MAXIMUM) / 2 / hitObjectManager.hitObjects.Count); Debug.Print(" max final hp: {0:00.00}%", HpBar.CurrentHp / 2); Debug.Print(" max combo: " + TotalHitsPossible); Debug.Print(string.Empty); return(testDrop); } while (true); }