mirror of
https://github.com/ppy/osu
synced 2025-03-04 02:19:45 +00:00
Merge pull request #26039 from smoogipoo/hp-drain-density
Add basic density consideration to HP drain
This commit is contained in:
commit
21e9e103fd
@ -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);
|
||||
|
72
osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs
Normal file
72
osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs
Normal file
@ -0,0 +1,72 @@
|
||||
// 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 osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Scoring
|
||||
{
|
||||
public partial class OsuHealthProcessor : DrainingHealthProcessor
|
||||
{
|
||||
public OsuHealthProcessor(double drainStartTime, double drainLenience = 0)
|
||||
: base(drainStartTime, drainLenience)
|
||||
{
|
||||
}
|
||||
|
||||
protected override int? GetDensityGroup(HitObject hitObject) => (hitObject as IHasComboInformation)?.ComboIndex;
|
||||
|
||||
protected override double GetHealthIncreaseFor(JudgementResult result)
|
||||
{
|
||||
switch (result.Type)
|
||||
{
|
||||
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:
|
||||
// When classic slider mechanics are enabled, this result comes from the tail.
|
||||
return 0.02;
|
||||
|
||||
case HitResult.LargeTickHit:
|
||||
switch (result.HitObject)
|
||||
{
|
||||
case SliderTick:
|
||||
return 0.015;
|
||||
|
||||
case SliderHeadCircle:
|
||||
case SliderTailCircle:
|
||||
case SliderRepeat:
|
||||
return 0.02;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case HitResult.Meh:
|
||||
return 0.002;
|
||||
|
||||
case HitResult.Ok:
|
||||
return 0.011;
|
||||
|
||||
case HitResult.Great:
|
||||
return 0.03;
|
||||
|
||||
case HitResult.SmallBonus:
|
||||
return 0.0085;
|
||||
|
||||
case HitResult.LargeBonus:
|
||||
return 0.01;
|
||||
}
|
||||
|
||||
return base.GetHealthIncreaseFor(result);
|
||||
}
|
||||
}
|
||||
}
|
@ -61,7 +61,9 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
protected readonly double DrainLenience;
|
||||
|
||||
private readonly List<(double time, double health)> healthIncreases = new List<(double, double)>();
|
||||
private readonly List<HealthIncrease> healthIncreases = new List<HealthIncrease>();
|
||||
private readonly Dictionary<int, double> densityMultiplierByGroup = new Dictionary<int, double>();
|
||||
|
||||
private double gameplayEndTime;
|
||||
private double targetMinimumHealth;
|
||||
|
||||
@ -133,14 +135,33 @@ namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
base.ApplyResultInternal(result);
|
||||
|
||||
if (!result.Type.IsBonus())
|
||||
healthIncreases.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result)));
|
||||
if (IsSimulating && !result.Type.IsBonus())
|
||||
{
|
||||
healthIncreases.Add(new HealthIncrease(
|
||||
result.HitObject.GetEndTime() + result.TimeOffset,
|
||||
GetHealthIncreaseFor(result),
|
||||
GetDensityGroup(result.HitObject)));
|
||||
}
|
||||
}
|
||||
|
||||
protected override double GetHealthIncreaseFor(JudgementResult result) => base.GetHealthIncreaseFor(result) * getDensityMultiplier(GetDensityGroup(result.HitObject));
|
||||
|
||||
private double getDensityMultiplier(int? group)
|
||||
{
|
||||
if (group == null)
|
||||
return 1;
|
||||
|
||||
return densityMultiplierByGroup.TryGetValue(group.Value, out double multiplier) ? multiplier : 1;
|
||||
}
|
||||
|
||||
protected virtual int? GetDensityGroup(HitObject hitObject) => null;
|
||||
|
||||
protected override void Reset(bool storeResults)
|
||||
{
|
||||
base.Reset(storeResults);
|
||||
|
||||
densityMultiplierByGroup.Clear();
|
||||
|
||||
if (storeResults)
|
||||
DrainRate = ComputeDrainRate();
|
||||
|
||||
@ -152,6 +173,24 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (healthIncreases.Count <= 1)
|
||||
return 0;
|
||||
|
||||
// Normalise the health gain during sections with higher densities.
|
||||
(int group, double avgIncrease)[] avgIncreasesByGroup = healthIncreases
|
||||
.Where(i => i.Group != null)
|
||||
.GroupBy(i => i.Group)
|
||||
.Select(g => ((int)g.Key!, g.Sum(i => i.Amount) / (g.Max(i => i.Time) - g.Min(i => i.Time) + 1)))
|
||||
.ToArray();
|
||||
|
||||
if (avgIncreasesByGroup.Length > 1)
|
||||
{
|
||||
double overallAverageIncrease = avgIncreasesByGroup.Average(g => g.avgIncrease);
|
||||
|
||||
foreach ((int group, double avgIncrease) in avgIncreasesByGroup)
|
||||
{
|
||||
// Reduce the health increase for groups that return more health than average.
|
||||
densityMultiplierByGroup[group] = Math.Min(1, overallAverageIncrease / avgIncrease);
|
||||
}
|
||||
}
|
||||
|
||||
int adjustment = 1;
|
||||
double result = 1;
|
||||
|
||||
@ -165,8 +204,8 @@ 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 currentTime = healthIncreases[i].Time;
|
||||
double lastTime = i > 0 ? healthIncreases[i - 1].Time : DrainStartTime;
|
||||
|
||||
while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= currentTime)
|
||||
{
|
||||
@ -177,10 +216,12 @@ namespace osu.Game.Rulesets.Scoring
|
||||
currentBreak++;
|
||||
}
|
||||
|
||||
double multiplier = getDensityMultiplier(healthIncreases[i].Group);
|
||||
|
||||
// Apply health adjustments
|
||||
currentHealth -= (currentTime - lastTime) * result;
|
||||
lowestHealth = Math.Min(lowestHealth, currentHealth);
|
||||
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health);
|
||||
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount * multiplier);
|
||||
|
||||
// Common scenario for when the drain rate is definitely too harsh
|
||||
if (lowestHealth < 0)
|
||||
@ -198,5 +239,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private record struct HealthIncrease(double Time, double Amount, int? Group);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user