mirror of
https://github.com/ppy/osu.git
synced 2024-11-15 13:47:24 +08:00
Merge pull request #30460 from Lawtrohux/t-speed-deviation
Implement stamina consideration for Mono (single-coloured) patterns.
This commit is contained in:
commit
c99c82ab08
@ -1,33 +1,55 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// 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.Preprocessing;
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the stamina coefficient of taiko difficulty.
|
/// Calculates the stamina coefficient of taiko difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Stamina : StrainDecaySkill
|
public class Stamina : StrainSkill
|
||||||
{
|
{
|
||||||
protected override double SkillMultiplier => 1.1;
|
private double skillMultiplier => 1.1;
|
||||||
protected override double StrainDecayBase => 0.4;
|
private double strainDecayBase => 0.4;
|
||||||
|
|
||||||
|
private readonly bool singleColourStamina;
|
||||||
|
|
||||||
|
private double currentStrain;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="Stamina"/> skill.
|
/// Creates a <see cref="Stamina"/> skill.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mods">Mods for use in skill calculations.</param>
|
/// <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)
|
: 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
[JsonProperty("stamina_difficulty")]
|
[JsonProperty("stamina_difficulty")]
|
||||||
public double StaminaDifficulty { get; set; }
|
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>
|
/// <summary>
|
||||||
/// The difficulty corresponding to the rhythm skill.
|
/// The difficulty corresponding to the rhythm skill.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -60,6 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
||||||
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
||||||
yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow);
|
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)
|
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||||
@ -69,6 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
||||||
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
||||||
OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW];
|
OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW];
|
||||||
|
MonoStaminaFactor = values[ATTRIB_ID_MONO_STAMINA_FACTOR];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
{
|
{
|
||||||
new Rhythm(mods),
|
new Rhythm(mods),
|
||||||
new Colour(mods),
|
new Colour(mods),
|
||||||
new Stamina(mods)
|
new Stamina(mods, false),
|
||||||
|
new Stamina(mods, true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,10 +80,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
Colour colour = (Colour)skills.First(x => x is Colour);
|
Colour colour = (Colour)skills.First(x => x is Colour);
|
||||||
Rhythm rhythm = (Rhythm)skills.First(x => x is Rhythm);
|
Rhythm rhythm = (Rhythm)skills.First(x => x is Rhythm);
|
||||||
Stamina stamina = (Stamina)skills.First(x => x is Stamina);
|
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 colourRating = colour.DifficultyValue() * colour_skill_multiplier;
|
||||||
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
||||||
double staminaRating = stamina.DifficultyValue() * stamina_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 combinedRating = combinedDifficultyValue(rhythm, colour, stamina);
|
||||||
double starRating = rescale(combinedRating * 1.4);
|
double starRating = rescale(combinedRating * 1.4);
|
||||||
@ -95,6 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
StarRating = starRating,
|
StarRating = starRating,
|
||||||
Mods = mods,
|
Mods = mods,
|
||||||
StaminaDifficulty = staminaRating,
|
StaminaDifficulty = staminaRating,
|
||||||
|
MonoStaminaFactor = monoStaminaFactor,
|
||||||
RhythmDifficulty = rhythmRating,
|
RhythmDifficulty = rhythmRating,
|
||||||
ColourDifficulty = colourRating,
|
ColourDifficulty = colourRating,
|
||||||
PeakDifficulty = combinedRating,
|
PeakDifficulty = combinedRating,
|
||||||
|
@ -42,18 +42,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
if (totalSuccessfulHits > 0)
|
if (totalSuccessfulHits > 0)
|
||||||
effectiveMissCount = Math.Max(1.0, 1000.0 / totalSuccessfulHits) * countMiss;
|
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;
|
bool isConvert = score.BeatmapInfo!.Ruleset.OnlineID != 1;
|
||||||
|
|
||||||
double multiplier = 1.13;
|
double multiplier = 1.13;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModHidden))
|
if (score.Mods.Any(m => m is ModHidden) && !isConvert)
|
||||||
multiplier *= 1.075;
|
multiplier *= 1.075;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModEasy))
|
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 accuracyValue = computeAccuracyValue(score, taikoAttributes, isConvert);
|
||||||
double totalValue =
|
double totalValue =
|
||||||
Math.Pow(
|
Math.Pow(
|
||||||
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.115) - 4.0, 2.25) / 1150.0;
|
||||||
|
|
||||||
@ -81,21 +81,25 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
difficultyValue *= Math.Pow(0.986, effectiveMissCount);
|
difficultyValue *= Math.Pow(0.986, effectiveMissCount);
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModEasy))
|
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;
|
difficultyValue *= 1.025;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModHardRock))
|
if (score.Mods.Any(m => m is ModHardRock))
|
||||||
difficultyValue *= 1.10;
|
difficultyValue *= 1.10;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
|
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)
|
if (estimatedUnstableRate == null)
|
||||||
return 0;
|
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)
|
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert)
|
||||||
|
@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23;
|
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_AIM_DIFFICULT_STRAIN_COUNT = 25;
|
||||||
protected const int ATTRIB_ID_OK_HIT_WINDOW = 27;
|
protected const int ATTRIB_ID_OK_HIT_WINDOW = 27;
|
||||||
|
protected const int ATTRIB_ID_MONO_STAMINA_FACTOR = 29;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mods which were applied to the beatmap.
|
/// The mods which were applied to the beatmap.
|
||||||
|
Loading…
Reference in New Issue
Block a user