1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 17:35:10 +08:00
This commit is contained in:
Dan Balasescu 2023-11-13 13:46:47 +09:00
parent 7713da499f
commit 98e6b7744b
No known key found for this signature in database
4 changed files with 47 additions and 120 deletions

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
/// Reference implementation for osu!stable's HP drain. /// Reference implementation for osu!stable's HP drain.
/// Cannot be used for gameplay. /// Cannot be used for gameplay.
/// </summary> /// </summary>
public partial class LegacyOsuHealthProcessor : LegacyDrainingHealthProcessor public partial class LegacyOsuHealthProcessor : DrainingHealthProcessor
{ {
private const double hp_bar_maximum = 200; private const double hp_bar_maximum = 200;
private const double hp_combo_geki = 14; 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_repeat = 4;
private const double hp_slider_tick = 3; private const double hp_slider_tick = 3;
public Action<string>? OnIterationFail;
public Action<string>? OnIterationSuccess;
private double lowestHpEver; private double lowestHpEver;
private double lowestHpEnd; private double lowestHpEnd;
private double lowestHpComboEnd; private double lowestHpComboEnd;
@ -187,13 +190,11 @@ namespace osu.Game.Rulesets.Osu.Scoring
if (fail) if (fail)
{ {
if (Log) OnIterationFail?.Invoke($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}");
Console.WriteLine($"FAILED drop {testDrop / hp_bar_maximum}: {failReason}");
continue; continue;
} }
if (Log) OnIterationSuccess?.Invoke($"PASSED drop {testDrop / hp_bar_maximum}");
Console.WriteLine($"PASSED drop {testDrop / hp_bar_maximum}");
return testDrop / hp_bar_maximum; return testDrop / hp_bar_maximum;
} while (true); } while (true);

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -12,8 +13,11 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring namespace osu.Game.Rulesets.Osu.Scoring
{ {
public partial class OsuHealthProcessor : LegacyDrainingHealthProcessor public partial class OsuHealthProcessor : DrainingHealthProcessor
{ {
public Action<string>? OnIterationFail;
public Action<string>? OnIterationSuccess;
private double lowestHpEver; private double lowestHpEver;
private double lowestHpEnd; private double lowestHpEnd;
private double hpRecoveryAvailable; private double hpRecoveryAvailable;
@ -141,13 +145,11 @@ namespace osu.Game.Rulesets.Osu.Scoring
if (fail) if (fail)
{ {
if (Log) OnIterationFail?.Invoke($"FAILED drop {testDrop}: {failReason}");
Console.WriteLine($"FAILED drop {testDrop}: {failReason}");
continue; continue;
} }
if (Log) OnIterationSuccess?.Invoke($"PASSED drop {testDrop}");
Console.WriteLine($"PASSED drop {testDrop}");
return testDrop; return testDrop;
} while (true); } while (true);

View File

@ -41,15 +41,29 @@ namespace osu.Game.Rulesets.Scoring
/// </summary> /// </summary>
private const double max_health_target = 0.4; private const double max_health_target = 0.4;
private IBeatmap beatmap; /// <summary>
private double gameplayEndTime; /// The drain rate as a proportion of the total health drained per millisecond.
/// </summary>
public double DrainRate { get; private set; } = 1;
private readonly double drainStartTime; /// <summary>
private readonly double drainLenience; /// The beatmap.
/// </summary>
protected IBeatmap Beatmap { get; private set; }
/// <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 readonly List<(double time, double health)> healthIncreases = new List<(double, double)>();
private double gameplayEndTime;
private double targetMinimumHealth; private double targetMinimumHealth;
private double drainRate = 1;
private PeriodTracker noDrainPeriodTracker; private PeriodTracker noDrainPeriodTracker;
@ -63,8 +77,8 @@ namespace osu.Game.Rulesets.Scoring
/// A value of 1 completely removes drain.</param> /// A value of 1 completely removes drain.</param>
public DrainingHealthProcessor(double drainStartTime, double drainLenience = 0) public DrainingHealthProcessor(double drainStartTime, double drainLenience = 0)
{ {
this.drainStartTime = drainStartTime; DrainStartTime = drainStartTime;
this.drainLenience = Math.Clamp(drainLenience, 0, 1); DrainLenience = Math.Clamp(drainLenience, 0, 1);
} }
protected override void Update() protected override void Update()
@ -75,16 +89,16 @@ namespace osu.Game.Rulesets.Scoring
return; 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 // 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 lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, DrainStartTime, gameplayEndTime);
double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime); double currentGameplayTime = Math.Clamp(Time.Current, DrainStartTime, gameplayEndTime);
if (drainLenience < 1) if (DrainLenience < 1)
Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime); Health.Value -= DrainRate * (currentGameplayTime - lastGameplayTime);
} }
public override void ApplyBeatmap(IBeatmap beatmap) public override void ApplyBeatmap(IBeatmap beatmap)
{ {
this.beatmap = beatmap; Beatmap = beatmap;
if (beatmap.HitObjects.Count > 0) if (beatmap.HitObjects.Count > 0)
gameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); 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); 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. // 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. // Ensure the target HP is within an acceptable range.
targetMinimumHealth = Math.Clamp(targetMinimumHealth, 0, 1); targetMinimumHealth = Math.Clamp(targetMinimumHealth, 0, 1);
@ -125,15 +139,15 @@ namespace osu.Game.Rulesets.Scoring
{ {
base.Reset(storeResults); base.Reset(storeResults);
drainRate = 1; DrainRate = 1;
if (storeResults) if (storeResults)
drainRate = computeDrainRate(); DrainRate = ComputeDrainRate();
healthIncreases.Clear(); healthIncreases.Clear();
} }
private double computeDrainRate() protected virtual double ComputeDrainRate()
{ {
if (healthIncreases.Count <= 1) if (healthIncreases.Count <= 1)
return 0; return 0;
@ -152,17 +166,17 @@ namespace osu.Game.Rulesets.Scoring
for (int i = 0; i < healthIncreases.Count; i++) for (int i = 0; i < healthIncreases.Count; i++)
{ {
double currentTime = healthIncreases[i].time; 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 // 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 // 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++; currentBreak++;
if (currentBreak >= 0) if (currentBreak >= 0)
lastTime = Math.Max(lastTime, beatmap.Breaks[currentBreak].EndTime); lastTime = Math.Max(lastTime, Beatmap.Breaks[currentBreak].EndTime);
} }
// Apply health adjustments // Apply health adjustments

View File

@ -1,90 +0,0 @@
// 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.
#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
{
/// <summary>
/// A <see cref="HealthProcessor"/> which continuously drains health.<br />
/// At HP=0, the minimum health reached for a perfect play is 95%.<br />
/// At HP=5, the minimum health reached for a perfect play is 70%.<br />
/// At HP=10, the minimum health reached for a perfect play is 30%.
/// </summary>
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; }
/// <summary>
/// Creates a new <see cref="DrainingHealthProcessor"/>.
/// </summary>
/// <param name="drainStartTime">The time after which draining should begin.</param>
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();
}
}