Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace recently-added HP drain density calculation with combo-end bonus #26083

Merged
merged 4 commits into from
Dec 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,80 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;

namespace osu.Game.Rulesets.Osu.Scoring
{
public partial class OsuHealthProcessor : DrainingHealthProcessor
{
private ComboResult currentComboResult = ComboResult.Perfect;

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)
{
if (IsSimulating)
return getHealthIncreaseFor(result);
Comment on lines +25 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the simulation run that determines the drain rate does ignores the existence of the end combo bonus entirely?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. This is not how stable does it but adding the combo bonus during simulation still makes it harder than it should be. Perhaps it could consider the lowest possible combo result, but let's get some feedback on how this feels the way it is first.


if (result.HitObject is not IHasComboInformation combo)
return getHealthIncreaseFor(result);
Comment on lines +28 to +29
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is for safety but this branch should be 100% dead...? OsuHitObject implements IHasComboInformation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose so. Imo there's nothing wrong with being safe. Might eventually move this to the base implementation if it works well too.


if (combo.NewCombo)
currentComboResult = ComboResult.Perfect;

switch (result.Type)
{
case HitResult.LargeTickMiss:
case HitResult.Ok:
setComboResult(ComboResult.Good);
break;

case HitResult.Meh:
case HitResult.Miss:
setComboResult(ComboResult.None);
break;
}

// The slider tail has a special judgement that can't accurately be described above.
if (result.HitObject is SliderTailCircle && !result.IsHit)
setComboResult(ComboResult.Good);

if (combo.LastInCombo && result.Type.IsHit())
{
switch (currentComboResult)
{
case ComboResult.Perfect:
return getHealthIncreaseFor(result) + 0.07;

case ComboResult.Good:
return getHealthIncreaseFor(result) + 0.05;

default:
return getHealthIncreaseFor(result) + 0.03;
}
}

return getHealthIncreaseFor(result);

void setComboResult(ComboResult comboResult) => currentComboResult = (ComboResult)Math.Min((int)currentComboResult, (int)comboResult);
}

protected override void Reset(bool storeResults)
{
base.Reset(storeResults);
currentComboResult = ComboResult.Perfect;
}

private double getHealthIncreaseFor(JudgementResult result)
{
switch (result.Type)
{
Expand Down
42 changes: 3 additions & 39 deletions osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public partial class DrainingHealthProcessor : HealthProcessor
protected readonly double DrainLenience;

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 @@ -139,29 +138,14 @@ protected override void ApplyResultInternal(JudgementResult result)
{
healthIncreases.Add(new HealthIncrease(
result.HitObject.GetEndTime() + result.TimeOffset,
GetHealthIncreaseFor(result),
GetDensityGroup(result.HitObject)));
GetHealthIncreaseFor(result)));
}
}

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 @@ -173,24 +157,6 @@ 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 @@ -216,12 +182,10 @@ 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].Amount * multiplier);
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount);

// Common scenario for when the drain rate is definitely too harsh
if (lowestHealth < 0)
Expand All @@ -240,6 +204,6 @@ protected virtual double ComputeDrainRate()
return result;
}

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