diff --git a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs index aa9312500b..f918868715 100644 --- a/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Scoring /// Reference implementation for osu!stable's HP drain. /// Cannot be used for gameplay. /// - public partial class LegacyOsuHealthProcessor : LegacyDrainingHealthProcessor + public partial class LegacyOsuHealthProcessor : DrainingHealthProcessor { private const double hp_bar_maximum = 200; private const double hp_combo_geki = 14; @@ -24,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Scoring private const double hp_slider_repeat = 4; private const double hp_slider_tick = 3; + public Action? OnIterationFail; + public Action? OnIterationSuccess; + private double lowestHpEver; private double lowestHpEnd; private double lowestHpComboEnd; @@ -187,13 +190,11 @@ namespace osu.Game.Rulesets.Osu.Scoring if (fail) { - if (Log) - Console.WriteLine($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}"); + OnIterationFail?.Invoke($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}"); continue; } - if (Log) - Console.WriteLine($"PASSED drop {testDrop / hp_bar_maximum}"); + OnIterationSuccess?.Invoke($"PASSED drop {testDrop / hp_bar_maximum}"); return testDrop / hp_bar_maximum; } while (true); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 2266cf9d33..3a096fca88 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; @@ -12,8 +13,11 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { - public partial class OsuHealthProcessor : LegacyDrainingHealthProcessor + public partial class OsuHealthProcessor : DrainingHealthProcessor { + public Action? OnIterationFail; + public Action? OnIterationSuccess; + private double lowestHpEver; private double lowestHpEnd; private double hpRecoveryAvailable; @@ -141,13 +145,11 @@ namespace osu.Game.Rulesets.Osu.Scoring if (fail) { - if (Log) - Console.WriteLine($"FAILED drop {testDrop}: {failReason}"); + OnIterationFail?.Invoke($"FAILED drop {testDrop}: {failReason}"); continue; } - if (Log) - Console.WriteLine($"PASSED drop {testDrop}"); + OnIterationSuccess?.Invoke($"PASSED drop {testDrop}"); return testDrop; } while (true); diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 2f81aa735e..a8808d08e5 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -41,15 +41,29 @@ namespace osu.Game.Rulesets.Scoring /// private const double max_health_target = 0.4; - private IBeatmap beatmap; - private double gameplayEndTime; + /// + /// The drain rate as a proportion of the total health drained per millisecond. + /// + public double DrainRate { get; private set; } = 1; - private readonly double drainStartTime; - private readonly double drainLenience; + /// + /// The beatmap. + /// + protected IBeatmap Beatmap { get; private set; } + + /// + /// The time at which health starts draining. + /// + protected readonly double DrainStartTime; + + /// + /// An amount of lenience to apply to the drain rate. + /// + protected readonly double DrainLenience; private readonly List<(double time, double health)> healthIncreases = new List<(double, double)>(); + private double gameplayEndTime; private double targetMinimumHealth; - private double drainRate = 1; private PeriodTracker noDrainPeriodTracker; @@ -63,8 +77,8 @@ namespace osu.Game.Rulesets.Scoring /// A value of 1 completely removes drain. public DrainingHealthProcessor(double drainStartTime, double drainLenience = 0) { - this.drainStartTime = drainStartTime; - this.drainLenience = Math.Clamp(drainLenience, 0, 1); + DrainStartTime = drainStartTime; + DrainLenience = Math.Clamp(drainLenience, 0, 1); } protected override void Update() @@ -75,16 +89,16 @@ namespace osu.Game.Rulesets.Scoring return; // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time - double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime); - double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime); + double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, DrainStartTime, gameplayEndTime); + double currentGameplayTime = Math.Clamp(Time.Current, DrainStartTime, gameplayEndTime); - if (drainLenience < 1) - Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime); + if (DrainLenience < 1) + Health.Value -= DrainRate * (currentGameplayTime - lastGameplayTime); } public override void ApplyBeatmap(IBeatmap beatmap) { - this.beatmap = beatmap; + Beatmap = beatmap; if (beatmap.HitObjects.Count > 0) gameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); @@ -105,7 +119,7 @@ namespace osu.Game.Rulesets.Scoring targetMinimumHealth = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, min_health_target, mid_health_target, max_health_target); // Add back a portion of the amount of HP to be drained, depending on the lenience requested. - targetMinimumHealth += drainLenience * (1 - targetMinimumHealth); + targetMinimumHealth += DrainLenience * (1 - targetMinimumHealth); // Ensure the target HP is within an acceptable range. targetMinimumHealth = Math.Clamp(targetMinimumHealth, 0, 1); @@ -125,15 +139,15 @@ namespace osu.Game.Rulesets.Scoring { base.Reset(storeResults); - drainRate = 1; + DrainRate = 1; if (storeResults) - drainRate = computeDrainRate(); + DrainRate = ComputeDrainRate(); healthIncreases.Clear(); } - private double computeDrainRate() + protected virtual double ComputeDrainRate() { if (healthIncreases.Count <= 1) return 0; @@ -152,17 +166,17 @@ namespace osu.Game.Rulesets.Scoring for (int i = 0; i < healthIncreases.Count; i++) { double currentTime = healthIncreases[i].time; - double lastTime = i > 0 ? healthIncreases[i - 1].time : drainStartTime; + double lastTime = i > 0 ? healthIncreases[i - 1].time : DrainStartTime; // Subtract any break time from the duration since the last object - if (beatmap.Breaks.Count > 0) + if (Beatmap.Breaks.Count > 0) { // Advance the last break occuring before the current time - while (currentBreak + 1 < beatmap.Breaks.Count && beatmap.Breaks[currentBreak + 1].EndTime < currentTime) + while (currentBreak + 1 < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak + 1].EndTime < currentTime) currentBreak++; if (currentBreak >= 0) - lastTime = Math.Max(lastTime, beatmap.Breaks[currentBreak].EndTime); + lastTime = Math.Max(lastTime, Beatmap.Breaks[currentBreak].EndTime); } // Apply health adjustments diff --git a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs deleted file mode 100644 index 5d2426e4b7..0000000000 --- a/osu.Game/Rulesets/Scoring/LegacyDrainingHealthProcessor.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Utils; - -namespace osu.Game.Rulesets.Scoring -{ - /// - /// A which continuously drains health.
- /// At HP=0, the minimum health reached for a perfect play is 95%.
- /// At HP=5, the minimum health reached for a perfect play is 70%.
- /// At HP=10, the minimum health reached for a perfect play is 30%. - ///
- public abstract partial class LegacyDrainingHealthProcessor : HealthProcessor - { - protected double DrainStartTime { get; } - protected double GameplayEndTime { get; private set; } - - protected IBeatmap Beatmap { get; private set; } - protected PeriodTracker NoDrainPeriodTracker { get; private set; } - - public bool Log { get; set; } - - public double DrainRate { get; private set; } - - /// - /// Creates a new . - /// - /// The time after which draining should begin. - protected LegacyDrainingHealthProcessor(double drainStartTime) - { - DrainStartTime = drainStartTime; - } - - protected override void Update() - { - base.Update(); - - if (NoDrainPeriodTracker?.IsInAny(Time.Current) == true) - return; - - // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time - double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, DrainStartTime, GameplayEndTime); - double currentGameplayTime = Math.Clamp(Time.Current, DrainStartTime, GameplayEndTime); - - Health.Value -= DrainRate * (currentGameplayTime - lastGameplayTime); - } - - public override void ApplyBeatmap(IBeatmap beatmap) - { - Beatmap = beatmap; - - if (beatmap.HitObjects.Count > 0) - GameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); - - NoDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period( - beatmap.HitObjects - .Select(hitObject => hitObject.GetEndTime()) - .Where(endTime => endTime <= breakPeriod.StartTime) - .DefaultIfEmpty(double.MinValue) - .Last(), - beatmap.HitObjects - .Select(hitObject => hitObject.StartTime) - .Where(startTime => startTime >= breakPeriod.EndTime) - .DefaultIfEmpty(double.MaxValue) - .First() - ))); - - base.ApplyBeatmap(beatmap); - } - - protected override void Reset(bool storeResults) - { - base.Reset(storeResults); - - DrainRate = 1; - - if (storeResults) - DrainRate = ComputeDrainRate(); - } - - protected abstract double ComputeDrainRate(); - } -}