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

Implement stamina consideration for Mono (single-coloured) patterns. #30460

Merged
merged 8 commits into from
Nov 4, 2024
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
34 changes: 28 additions & 6 deletions osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,55 @@
// 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 System;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;

namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
{
/// <summary>
/// Calculates the stamina coefficient of taiko difficulty.
/// </summary>
public class Stamina : StrainDecaySkill
public class Stamina : StrainSkill
{
protected override double SkillMultiplier => 1.1;
protected override double StrainDecayBase => 0.4;
private double skillMultiplier => 1.1;
private double strainDecayBase => 0.4;

private readonly bool singleColourStamina;

private double currentStrain;

/// <summary>
/// Creates a <see cref="Stamina"/> skill.
/// </summary>
/// <param name="mods">Mods for use in skill calculations.</param>
public Stamina(Mod[] mods)
/// <param name="singleColourStamina">Reads when Stamina is from a single coloured pattern.</param>
public Stamina(Mod[] mods, bool singleColourStamina)
: base(mods)
{
this.singleColourStamina = singleColourStamina;
}

protected override double StrainValueOf(DifficultyHitObject current)
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);

protected override double StrainValueAt(DifficultyHitObject current)
{
return StaminaEvaluator.EvaluateDifficultyOf(current);
currentStrain *= strainDecay(current.DeltaTime);
currentStrain += StaminaEvaluator.EvaluateDifficultyOf(current) * skillMultiplier;

// Safely prevents previous strains from shifting as new notes are added.
var currentObject = current as TaikoDifficultyHitObject;
int index = currentObject?.Colour.MonoStreak?.HitObjects.IndexOf(currentObject) ?? 0;

if (singleColourStamina)
return currentStrain / (1 + Math.Exp(-(index - 10) / 2.0));

return currentStrain;
}

protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => singleColourStamina ? 0 : currentStrain * strainDecay(time - current.Previous(0).StartTime);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ public class TaikoDifficultyAttributes : DifficultyAttributes
[JsonProperty("stamina_difficulty")]
public double StaminaDifficulty { get; set; }

/// <summary>
/// The ratio of stamina difficulty from mono-color (single colour) streams to total stamina difficulty.
/// </summary>
[JsonProperty("mono_stamina_factor")]
public double MonoStaminaFactor { get; set; }

/// <summary>
/// The difficulty corresponding to the rhythm skill.
/// </summary>
Expand Down Expand Up @@ -60,6 +66,7 @@ public class TaikoDifficultyAttributes : DifficultyAttributes
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow);
yield return (ATTRIB_ID_MONO_STAMINA_FACTOR, MonoStaminaFactor);
}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
Expand All @@ -69,6 +76,7 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> val
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW];
MonoStaminaFactor = values[ATTRIB_ID_MONO_STAMINA_FACTOR];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clo
{
new Rhythm(mods),
new Colour(mods),
new Stamina(mods)
new Stamina(mods, false),
new Stamina(mods, true)
};
}

Expand Down Expand Up @@ -79,10 +80,13 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
Colour colour = (Colour)skills.First(x => x is Colour);
Rhythm rhythm = (Rhythm)skills.First(x => x is Rhythm);
Stamina stamina = (Stamina)skills.First(x => x is Stamina);
Stamina singleColourStamina = (Stamina)skills.Last(x => x is Stamina);

double colourRating = colour.DifficultyValue() * colour_skill_multiplier;
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier;
double monoStaminaRating = singleColourStamina.DifficultyValue() * stamina_skill_multiplier;
double monoStaminaFactor = staminaRating == 0 ? 1 : Math.Pow(monoStaminaRating / staminaRating, 5);

double combinedRating = combinedDifficultyValue(rhythm, colour, stamina);
double starRating = rescale(combinedRating * 1.4);
Expand All @@ -95,6 +99,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
StarRating = starRating,
Mods = mods,
StaminaDifficulty = staminaRating,
MonoStaminaFactor = monoStaminaFactor,
RhythmDifficulty = rhythmRating,
ColourDifficulty = colourRating,
PeakDifficulty = combinedRating,
Expand Down
22 changes: 13 additions & 9 deletions osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,18 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
if (totalSuccessfulHits > 0)
effectiveMissCount = Math.Max(1.0, 1000.0 / totalSuccessfulHits) * countMiss;

// TODO: The detection of rulesets is temporary until the leftover old skills have been reworked.
// Converts are detected and omitted from mod-specific bonuses due to the scope of current difficulty calculation.
bool isConvert = score.BeatmapInfo!.Ruleset.OnlineID != 1;

double multiplier = 1.13;

if (score.Mods.Any(m => m is ModHidden))
if (score.Mods.Any(m => m is ModHidden) && !isConvert)
multiplier *= 1.075;

if (score.Mods.Any(m => m is ModEasy))
multiplier *= 0.975;
multiplier *= 0.950;

double difficultyValue = computeDifficultyValue(score, taikoAttributes, isConvert);
double difficultyValue = computeDifficultyValue(score, taikoAttributes);
double accuracyValue = computeAccuracyValue(score, taikoAttributes, isConvert);
double totalValue =
Math.Pow(
Expand All @@ -71,7 +71,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
};
}

private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert)
private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
{
double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.115) - 4.0, 2.25) / 1150.0;

Expand All @@ -81,21 +81,25 @@ private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes
difficultyValue *= Math.Pow(0.986, effectiveMissCount);

if (score.Mods.Any(m => m is ModEasy))
difficultyValue *= 0.985;
difficultyValue *= 0.90;

if (score.Mods.Any(m => m is ModHidden) && !isConvert)
if (score.Mods.Any(m => m is ModHidden))
difficultyValue *= 1.025;

if (score.Mods.Any(m => m is ModHardRock))
difficultyValue *= 1.10;

if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
difficultyValue *= 1.050 * lengthBonus;
difficultyValue *= Math.Max(1, 1.050 - Math.Min(attributes.MonoStaminaFactor / 50, 1) * lengthBonus);

if (estimatedUnstableRate == null)
return 0;

return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUnstableRate.Value)), 2.0);
// Scale accuracy more harshly on nearly-completely mono (single coloured) speed maps.
double accScalingExponent = 2 + attributes.MonoStaminaFactor;
double accScalingShift = 300 - 100 * attributes.MonoStaminaFactor;

return difficultyValue * Math.Pow(SpecialFunctions.Erf(accScalingShift / (Math.Sqrt(2) * estimatedUnstableRate.Value)), accScalingExponent);
}

private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert)
Expand Down
1 change: 1 addition & 0 deletions osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class DifficultyAttributes
protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23;
protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 25;
protected const int ATTRIB_ID_OK_HIT_WINDOW = 27;
protected const int ATTRIB_ID_MONO_STAMINA_FACTOR = 29;

/// <summary>
/// The mods which were applied to the beatmap.
Expand Down
Loading