Skip to content

Commit

Permalink
Merge pull request #30460 from Lawtrohux/t-speed-deviation
Browse files Browse the repository at this point in the history
Implement stamina consideration for Mono (single-coloured) patterns.
  • Loading branch information
peppy authored Nov 4, 2024
2 parents 7d39765 + 4eee1f4 commit c99c82a
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 16 deletions.
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

0 comments on commit c99c82a

Please sign in to comment.