Skip to content

Commit

Permalink
Merge pull request #26039 from smoogipoo/hp-drain-density
Browse files Browse the repository at this point in the history
Add basic density consideration to HP drain
  • Loading branch information
peppy authored Dec 22, 2023
2 parents 32e1b27 + a018550 commit 21e9e10
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 6 deletions.
2 changes: 2 additions & 0 deletions osu.Game.Rulesets.Osu/OsuRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class OsuRuleset : Ruleset, ILegacyRuleset

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);
Expand Down
72 changes: 72 additions & 0 deletions osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. 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);
}
}
}
55 changes: 49 additions & 6 deletions osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ public partial class DrainingHealthProcessor : HealthProcessor
/// </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;

Expand Down Expand Up @@ -133,14 +135,33 @@ protected override void ApplyResultInternal(JudgementResult result)
{
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();

Expand All @@ -152,6 +173,24 @@ protected virtual double ComputeDrainRate()
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;

Expand All @@ -165,8 +204,8 @@ protected virtual double ComputeDrainRate()

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)
{
Expand All @@ -177,10 +216,12 @@ protected virtual double ComputeDrainRate()
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)
Expand All @@ -198,5 +239,7 @@ protected virtual double ComputeDrainRate()

return result;
}

private record struct HealthIncrease(double Time, double Amount, int? Group);
}
}

0 comments on commit 21e9e10

Please sign in to comment.