mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 11:37:28 +08:00
Merge pull request #25418 from smoogipoo/hp-drain-v1-2
Add `OsuHealthProcessor` that uses the legacy drain rate algorithm
This commit is contained in:
commit
52dc02fd32
@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
|
||||
|
||||
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new OsuHealthProcessor(drainStartTime);
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this);
|
||||
|
||||
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
|
||||
|
215
osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs
Normal file
215
osu.Game.Rulesets.Osu/Scoring/LegacyOsuHealthProcessor.cs
Normal file
@ -0,0 +1,215 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Scoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Reference implementation for osu!stable's HP drain.
|
||||
/// Cannot be used for gameplay.
|
||||
/// </summary>
|
||||
public partial class LegacyOsuHealthProcessor : DrainingHealthProcessor
|
||||
{
|
||||
private const double hp_bar_maximum = 200;
|
||||
private const double hp_combo_geki = 14;
|
||||
private const double hp_hit_300 = 6;
|
||||
private const double hp_slider_repeat = 4;
|
||||
private const double hp_slider_tick = 3;
|
||||
|
||||
public Action<string>? OnIterationFail;
|
||||
public Action<string>? OnIterationSuccess;
|
||||
public bool ApplyComboEndBonus { get; set; } = true;
|
||||
|
||||
private double lowestHpEver;
|
||||
private double lowestHpEnd;
|
||||
private double lowestHpComboEnd;
|
||||
private double hpRecoveryAvailable;
|
||||
private double hpMultiplierNormal;
|
||||
private double hpMultiplierComboEnd;
|
||||
|
||||
public LegacyOsuHealthProcessor(double drainStartTime)
|
||||
: base(drainStartTime)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 195, 160, 60);
|
||||
lowestHpComboEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 170, 80);
|
||||
lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 198, 180, 80);
|
||||
hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 8, 4, 0);
|
||||
|
||||
base.ApplyBeatmap(beatmap);
|
||||
}
|
||||
|
||||
protected override void ApplyResultInternal(JudgementResult result)
|
||||
{
|
||||
if (!IsSimulating)
|
||||
throw new NotSupportedException("The legacy osu! health processor is not supported for gameplay.");
|
||||
}
|
||||
|
||||
protected override void RevertResultInternal(JudgementResult result)
|
||||
{
|
||||
if (!IsSimulating)
|
||||
throw new NotSupportedException("The legacy osu! health processor is not supported for gameplay.");
|
||||
}
|
||||
|
||||
protected override void Reset(bool storeResults)
|
||||
{
|
||||
hpMultiplierNormal = 1;
|
||||
hpMultiplierComboEnd = 1;
|
||||
|
||||
base.Reset(storeResults);
|
||||
}
|
||||
|
||||
protected override double ComputeDrainRate()
|
||||
{
|
||||
double testDrop = 0.05;
|
||||
double currentHp;
|
||||
double currentHpUncapped;
|
||||
|
||||
do
|
||||
{
|
||||
currentHp = hp_bar_maximum;
|
||||
currentHpUncapped = hp_bar_maximum;
|
||||
|
||||
double lowestHp = currentHp;
|
||||
double lastTime = DrainStartTime;
|
||||
int currentBreak = 0;
|
||||
bool fail = false;
|
||||
int comboTooLowCount = 0;
|
||||
string failReason = string.Empty;
|
||||
|
||||
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
HitObject h = Beatmap.HitObjects[i];
|
||||
|
||||
// Find active break (between current and lastTime)
|
||||
double localLastTime = lastTime;
|
||||
double breakTime = 0;
|
||||
|
||||
// Subtract any break time from the duration since the last object
|
||||
if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count)
|
||||
{
|
||||
BreakPeriod e = Beatmap.Breaks[currentBreak];
|
||||
|
||||
if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime)
|
||||
{
|
||||
// consider break start equal to object end time for version 8+ since drain stops during this time
|
||||
breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime;
|
||||
currentBreak++;
|
||||
}
|
||||
}
|
||||
|
||||
reduceHp(testDrop * (h.StartTime - lastTime - breakTime));
|
||||
|
||||
lastTime = h.GetEndTime();
|
||||
|
||||
if (currentHp < lowestHp)
|
||||
lowestHp = currentHp;
|
||||
|
||||
if (currentHp <= lowestHpEver)
|
||||
{
|
||||
fail = true;
|
||||
testDrop *= 0.96;
|
||||
failReason = $"hp too low ({currentHp / hp_bar_maximum} < {lowestHpEver / hp_bar_maximum})";
|
||||
break;
|
||||
}
|
||||
|
||||
double hpReduction = testDrop * (h.GetEndTime() - h.StartTime);
|
||||
double hpOverkill = Math.Max(0, hpReduction - currentHp);
|
||||
reduceHp(hpReduction);
|
||||
|
||||
if (h is Slider slider)
|
||||
{
|
||||
for (int j = 0; j < slider.RepeatCount + 2; j++)
|
||||
increaseHp(hpMultiplierNormal * hp_slider_repeat);
|
||||
foreach (var _ in slider.NestedHitObjects.OfType<SliderTick>())
|
||||
increaseHp(hpMultiplierNormal * hp_slider_tick);
|
||||
}
|
||||
else if (h is Spinner spinner)
|
||||
{
|
||||
foreach (var _ in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick))
|
||||
increaseHp(hpMultiplierNormal * 1.7);
|
||||
}
|
||||
|
||||
if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver)
|
||||
{
|
||||
fail = true;
|
||||
testDrop *= 0.96;
|
||||
failReason = $"overkill ({currentHp / hp_bar_maximum} - {hpOverkill / hp_bar_maximum} <= {lowestHpEver / hp_bar_maximum})";
|
||||
break;
|
||||
}
|
||||
|
||||
if (ApplyComboEndBonus && (i == Beatmap.HitObjects.Count - 1 || ((OsuHitObject)Beatmap.HitObjects[i + 1]).NewCombo))
|
||||
{
|
||||
increaseHp(hpMultiplierComboEnd * hp_combo_geki + hpMultiplierNormal * hp_hit_300);
|
||||
|
||||
if (currentHp < lowestHpComboEnd)
|
||||
{
|
||||
if (++comboTooLowCount > 2)
|
||||
{
|
||||
hpMultiplierComboEnd *= 1.07;
|
||||
hpMultiplierNormal *= 1.03;
|
||||
fail = true;
|
||||
failReason = $"combo end hp too low ({currentHp / hp_bar_maximum} < {lowestHpComboEnd / hp_bar_maximum})";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
increaseHp(hpMultiplierNormal * hp_hit_300);
|
||||
}
|
||||
|
||||
if (!fail && currentHp < lowestHpEnd)
|
||||
{
|
||||
fail = true;
|
||||
testDrop *= 0.94;
|
||||
hpMultiplierComboEnd *= 1.01;
|
||||
hpMultiplierNormal *= 1.01;
|
||||
failReason = $"end hp too low ({currentHp / hp_bar_maximum} < {lowestHpEnd / hp_bar_maximum})";
|
||||
}
|
||||
|
||||
double recovery = (currentHpUncapped - hp_bar_maximum) / Beatmap.HitObjects.Count;
|
||||
|
||||
if (!fail && recovery < hpRecoveryAvailable)
|
||||
{
|
||||
fail = true;
|
||||
testDrop *= 0.96;
|
||||
hpMultiplierComboEnd *= 1.02;
|
||||
hpMultiplierNormal *= 1.01;
|
||||
failReason = $"recovery too low ({recovery / hp_bar_maximum} < {hpRecoveryAvailable / hp_bar_maximum})";
|
||||
}
|
||||
|
||||
if (fail)
|
||||
{
|
||||
OnIterationFail?.Invoke($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}");
|
||||
continue;
|
||||
}
|
||||
|
||||
OnIterationSuccess?.Invoke($"PASSED drop {testDrop / hp_bar_maximum}");
|
||||
return testDrop / hp_bar_maximum;
|
||||
} while (true);
|
||||
|
||||
void reduceHp(double amount)
|
||||
{
|
||||
currentHpUncapped = Math.Max(0, currentHpUncapped - amount);
|
||||
currentHp = Math.Max(0, currentHp - amount);
|
||||
}
|
||||
|
||||
void increaseHp(double amount)
|
||||
{
|
||||
currentHpUncapped += amount;
|
||||
currentHp = Math.Max(0, Math.Min(hp_bar_maximum, currentHp + amount));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
223
osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs
Normal file
223
osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs
Normal file
@ -0,0 +1,223 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Scoring
|
||||
{
|
||||
public partial class OsuHealthProcessor : DrainingHealthProcessor
|
||||
{
|
||||
public Action<string>? OnIterationFail;
|
||||
public Action<string>? OnIterationSuccess;
|
||||
|
||||
private double lowestHpEver;
|
||||
private double lowestHpEnd;
|
||||
private double hpRecoveryAvailable;
|
||||
private double hpMultiplierNormal;
|
||||
|
||||
public OsuHealthProcessor(double drainStartTime)
|
||||
: base(drainStartTime)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
lowestHpEver = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.975, 0.8, 0.3);
|
||||
lowestHpEnd = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.99, 0.9, 0.4);
|
||||
hpRecoveryAvailable = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.04, 0.02, 0);
|
||||
|
||||
base.ApplyBeatmap(beatmap);
|
||||
}
|
||||
|
||||
protected override void Reset(bool storeResults)
|
||||
{
|
||||
hpMultiplierNormal = 1;
|
||||
base.Reset(storeResults);
|
||||
}
|
||||
|
||||
protected override double ComputeDrainRate()
|
||||
{
|
||||
double testDrop = 0.00025;
|
||||
double currentHp;
|
||||
double currentHpUncapped;
|
||||
|
||||
while (true)
|
||||
{
|
||||
currentHp = 1;
|
||||
currentHpUncapped = 1;
|
||||
|
||||
double lowestHp = currentHp;
|
||||
double lastTime = DrainStartTime;
|
||||
int currentBreak = 0;
|
||||
bool fail = false;
|
||||
|
||||
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
HitObject h = Beatmap.HitObjects[i];
|
||||
|
||||
// Find active break (between current and lastTime)
|
||||
double localLastTime = lastTime;
|
||||
double breakTime = 0;
|
||||
|
||||
// TODO: This doesn't handle overlapping/sequential breaks correctly (/b/614).
|
||||
// Subtract any break time from the duration since the last object
|
||||
// Note that this method is a bit convoluted, but matches stable code for compatibility.
|
||||
if (Beatmap.Breaks.Count > 0 && currentBreak < Beatmap.Breaks.Count)
|
||||
{
|
||||
BreakPeriod e = Beatmap.Breaks[currentBreak];
|
||||
|
||||
if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime)
|
||||
{
|
||||
// consider break start equal to object end time for version 8+ since drain stops during this time
|
||||
breakTime = (Beatmap.BeatmapInfo.BeatmapVersion < 8) ? (e.EndTime - e.StartTime) : e.EndTime - localLastTime;
|
||||
currentBreak++;
|
||||
}
|
||||
}
|
||||
|
||||
reduceHp(testDrop * (h.StartTime - lastTime - breakTime));
|
||||
|
||||
lastTime = h.GetEndTime();
|
||||
|
||||
if (currentHp < lowestHp)
|
||||
lowestHp = currentHp;
|
||||
|
||||
if (currentHp <= lowestHpEver)
|
||||
{
|
||||
fail = true;
|
||||
testDrop *= 0.96;
|
||||
OnIterationFail?.Invoke($"FAILED drop {testDrop}: hp too low ({currentHp} < {lowestHpEver})");
|
||||
break;
|
||||
}
|
||||
|
||||
double hpReduction = testDrop * (h.GetEndTime() - h.StartTime);
|
||||
double hpOverkill = Math.Max(0, hpReduction - currentHp);
|
||||
reduceHp(hpReduction);
|
||||
|
||||
switch (h)
|
||||
{
|
||||
case Slider slider:
|
||||
{
|
||||
foreach (var nested in slider.NestedHitObjects)
|
||||
increaseHp(nested);
|
||||
break;
|
||||
}
|
||||
|
||||
case Spinner spinner:
|
||||
{
|
||||
foreach (var nested in spinner.NestedHitObjects.Where(t => t is not SpinnerBonusTick))
|
||||
increaseHp(nested);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Because HP is capped during the above increases, long sliders (with many ticks) or spinners
|
||||
// will appear to overkill at lower drain levels than they should. However, it is also not correct to simply use the uncapped version.
|
||||
if (hpOverkill > 0 && currentHp - hpOverkill <= lowestHpEver)
|
||||
{
|
||||
fail = true;
|
||||
testDrop *= 0.96;
|
||||
OnIterationFail?.Invoke($"FAILED drop {testDrop}: overkill ({currentHp} - {hpOverkill} <= {lowestHpEver})");
|
||||
break;
|
||||
}
|
||||
|
||||
increaseHp(h);
|
||||
}
|
||||
|
||||
if (!fail && currentHp < lowestHpEnd)
|
||||
{
|
||||
fail = true;
|
||||
testDrop *= 0.94;
|
||||
hpMultiplierNormal *= 1.01;
|
||||
OnIterationFail?.Invoke($"FAILED drop {testDrop}: end hp too low ({currentHp} < {lowestHpEnd})");
|
||||
}
|
||||
|
||||
double recovery = (currentHpUncapped - 1) / Beatmap.HitObjects.Count;
|
||||
|
||||
if (!fail && recovery < hpRecoveryAvailable)
|
||||
{
|
||||
fail = true;
|
||||
testDrop *= 0.96;
|
||||
hpMultiplierNormal *= 1.01;
|
||||
OnIterationFail?.Invoke($"FAILED drop {testDrop}: recovery too low ({recovery} < {hpRecoveryAvailable})");
|
||||
}
|
||||
|
||||
if (!fail)
|
||||
{
|
||||
OnIterationSuccess?.Invoke($"PASSED drop {testDrop}");
|
||||
return testDrop;
|
||||
}
|
||||
}
|
||||
|
||||
void reduceHp(double amount)
|
||||
{
|
||||
currentHpUncapped = Math.Max(0, currentHpUncapped - amount);
|
||||
currentHp = Math.Max(0, currentHp - amount);
|
||||
}
|
||||
|
||||
void increaseHp(HitObject hitObject)
|
||||
{
|
||||
double amount = healthIncreaseFor(hitObject, hitObject.CreateJudgement().MaxResult);
|
||||
currentHpUncapped += amount;
|
||||
currentHp = Math.Max(0, Math.Min(1, currentHp + amount));
|
||||
}
|
||||
}
|
||||
|
||||
protected override double GetHealthIncreaseFor(JudgementResult result) => healthIncreaseFor(result.HitObject, result.Type);
|
||||
|
||||
private double healthIncreaseFor(HitObject hitObject, HitResult result)
|
||||
{
|
||||
double increase = 0;
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.SmallTickMiss:
|
||||
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14);
|
||||
|
||||
case HitResult.LargeTickMiss:
|
||||
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14);
|
||||
|
||||
case HitResult.Miss:
|
||||
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.03, -0.125, -0.2);
|
||||
|
||||
case HitResult.SmallTickHit:
|
||||
// This result always comes from the slider tail, which is judged the same as a repeat.
|
||||
increase = 0.02;
|
||||
break;
|
||||
|
||||
case HitResult.LargeTickHit:
|
||||
// This result comes from either a slider tick or repeat.
|
||||
increase = hitObject is SliderTick ? 0.015 : 0.02;
|
||||
break;
|
||||
|
||||
case HitResult.Meh:
|
||||
increase = 0.002;
|
||||
break;
|
||||
|
||||
case HitResult.Ok:
|
||||
increase = 0.011;
|
||||
break;
|
||||
|
||||
case HitResult.Great:
|
||||
increase = 0.03;
|
||||
break;
|
||||
|
||||
case HitResult.SmallBonus:
|
||||
increase = 0.0085;
|
||||
break;
|
||||
|
||||
case HitResult.LargeBonus:
|
||||
increase = 0.01;
|
||||
break;
|
||||
}
|
||||
|
||||
return hpMultiplierNormal * increase;
|
||||
}
|
||||
}
|
||||
}
|
@ -41,16 +41,29 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
private const double max_health_target = 0.4;
|
||||
|
||||
private IBeatmap beatmap;
|
||||
/// <summary>
|
||||
/// The drain rate as a proportion of the total health drained per millisecond.
|
||||
/// </summary>
|
||||
public double DrainRate { get; private set; }
|
||||
|
||||
private double gameplayEndTime;
|
||||
/// <summary>
|
||||
/// The beatmap.
|
||||
/// </summary>
|
||||
protected IBeatmap Beatmap { get; private set; }
|
||||
|
||||
private readonly double drainStartTime;
|
||||
private readonly double drainLenience;
|
||||
/// <summary>
|
||||
/// The time at which health starts draining.
|
||||
/// </summary>
|
||||
protected readonly double DrainStartTime;
|
||||
|
||||
/// <summary>
|
||||
/// An amount of lenience to apply to the drain rate.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
@ -64,8 +77,8 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// A value of 1 completely removes drain.</param>
|
||||
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()
|
||||
@ -76,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();
|
||||
@ -106,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);
|
||||
@ -126,15 +139,13 @@ namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
base.Reset(storeResults);
|
||||
|
||||
drainRate = 1;
|
||||
|
||||
if (storeResults)
|
||||
drainRate = computeDrainRate();
|
||||
DrainRate = ComputeDrainRate();
|
||||
|
||||
healthIncreases.Clear();
|
||||
}
|
||||
|
||||
private double computeDrainRate()
|
||||
protected virtual double ComputeDrainRate()
|
||||
{
|
||||
if (healthIncreases.Count <= 1)
|
||||
return 0;
|
||||
@ -153,17 +164,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
|
||||
|
Loading…
Reference in New Issue
Block a user