diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs
new file mode 100644
index 0000000000..159f9a4508
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/ColourEvaluator.cs
@@ -0,0 +1,33 @@
+using System;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
+{
+ public class ColourEvaluator
+ {
+ private static double sigmoid(double val, double center, double width)
+ {
+ return Math.Tanh(Math.E * -(val - center) / width);
+ }
+
+ public static double EvaluateDifficultyOf(DifficultyHitObject current)
+ {
+ TaikoDifficultyHitObject taikoCurrent = (TaikoDifficultyHitObject)current;
+ TaikoDifficultyHitObjectColour colour = taikoCurrent.Colour;
+ if (colour == null) return 0;
+ double objectStrain = 1;
+ if (colour.Delta)
+ {
+ objectStrain /= Math.Pow(colour.DeltaRunLength, 0.25);
+ }
+ else
+ {
+ objectStrain *= sigmoid(colour.DeltaRunLength, 3, 3) * 0.3 + 0.3;
+ }
+ objectStrain *= -sigmoid(colour.RepetitionInterval, 8, 8);
+ // Console.WriteLine($"{current.StartTime},{colour.GetHashCode()},{colour.Delta},{colour.DeltaRunLength},{colour.RepetitionInterval},{objectStrain}");
+ return objectStrain;
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
index b34e87bc83..8d2eadafe1 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
@@ -20,6 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
///
public readonly TaikoDifficultyHitObjectRhythm Rhythm;
+ ///
+ /// Colour data for this hit object. This is used by colour evaluator to calculate colour, but can be used
+ /// differently by other skills in the future.
+ ///
public readonly TaikoDifficultyHitObjectColour Colour;
///
@@ -45,7 +49,19 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
HitType = currentHit?.Type;
// Need to be done after HitType is set.
- Colour = TaikoDifficultyHitObjectColour.GetInstanceFor(this, (TaikoDifficultyHitObject) objects.LastOrDefault());
+ if (HitType != null)
+ {
+ // Get previous hit object, while skipping one that does not have defined colour (sliders and spinners).
+ // Without skipping through these, sliders and spinners would have contributed to a colour change for the next note.
+ TaikoDifficultyHitObject previousHitObject = (TaikoDifficultyHitObject)objects.LastOrDefault();
+ while (previousHitObject != null && previousHitObject.Colour == null)
+ {
+ previousHitObject = (TaikoDifficultyHitObject)previousHitObject.Previous(0);
+ }
+
+ Colour = TaikoDifficultyHitObjectColour.GetInstanceFor(this);
+ }
+
}
///
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectColour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectColour.cs
index a55fdc6e9f..ce65fd0552 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectColour.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObjectColour.cs
@@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
/// as a static function instead of constructor to allow for reusing existing instances.
/// TODO: findRepetitionInterval needs to be called a final time after all hitObjects have been processed.
///
- public static TaikoDifficultyHitObjectColour GetInstanceFor(
- TaikoDifficultyHitObject hitObject, TaikoDifficultyHitObject lastObject)
+ public static TaikoDifficultyHitObjectColour GetInstanceFor(TaikoDifficultyHitObject hitObject)
{
+ TaikoDifficultyHitObject lastObject = (TaikoDifficultyHitObject) hitObject.Previous(0);
TaikoDifficultyHitObjectColour previous = lastObject?.Colour;
bool delta = lastObject == null || hitObject.HitType != lastObject.HitType;
if (previous != null && delta == previous.Delta)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
index 0c17ca66b9..9a8b350e22 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
@@ -6,7 +6,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
@@ -19,27 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
protected override double SkillMultiplier => 1;
protected override double StrainDecayBase => 0.4;
- ///
- /// Maximum number of entries to keep in .
- ///
- private const int mono_history_max_length = 5;
-
- ///
- /// Queue with the lengths of the last most recent mono (single-colour) patterns,
- /// with the most recent value at the end of the queue.
- ///
- private readonly LimitedCapacityQueue monoHistory = new LimitedCapacityQueue(mono_history_max_length);
-
- ///
- /// The of the last object hit before the one being considered.
- ///
- private HitType? previousHitType;
-
- ///
- /// Length of the current mono pattern.
- ///
- private int currentMonoLength;
-
public Colour(Mod[] mods)
: base(mods)
{
@@ -47,95 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
protected override double StrainValueOf(DifficultyHitObject current)
{
- // changing from/to a drum roll or a swell does not constitute a colour change.
- // hits spaced more than a second apart are also exempt from colour strain.
- if (!(current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000))
- {
- monoHistory.Clear();
-
- var currentHit = current.BaseObject as Hit;
- currentMonoLength = currentHit != null ? 1 : 0;
- previousHitType = currentHit?.Type;
-
- return 0.0;
- }
-
- var taikoCurrent = (TaikoDifficultyHitObject)current;
-
- double objectStrain = 0.0;
-
- if (previousHitType != null && taikoCurrent.HitType != previousHitType)
- {
- // The colour has changed.
- objectStrain = 1.0;
-
- if (monoHistory.Count < 2)
- {
- // There needs to be at least two streaks to determine a strain.
- objectStrain = 0.0;
- }
- else if ((monoHistory[^1] + currentMonoLength) % 2 == 0)
- {
- // The last streak in the history is guaranteed to be a different type to the current streak.
- // If the total number of notes in the two streaks is even, nullify this object's strain.
- objectStrain = 0.0;
- }
-
- objectStrain *= repetitionPenalties();
- currentMonoLength = 1;
- }
- else
- {
- currentMonoLength += 1;
- }
-
- previousHitType = taikoCurrent.HitType;
- return objectStrain;
+ return ColourEvaluator.EvaluateDifficultyOf(current);
}
-
- ///
- /// The penalty to apply due to the length of repetition in colour streaks.
- ///
- private double repetitionPenalties()
- {
- const int most_recent_patterns_to_compare = 2;
- double penalty = 1.0;
-
- monoHistory.Enqueue(currentMonoLength);
-
- for (int start = monoHistory.Count - most_recent_patterns_to_compare - 1; start >= 0; start--)
- {
- if (!isSamePattern(start, most_recent_patterns_to_compare))
- continue;
-
- int notesSince = 0;
- for (int i = start; i < monoHistory.Count; i++) notesSince += monoHistory[i];
- penalty *= repetitionPenalty(notesSince);
- break;
- }
-
- return penalty;
- }
-
- ///
- /// Determines whether the last patterns have repeated in the history
- /// of single-colour note sequences, starting from .
- ///
- private bool isSamePattern(int start, int mostRecentPatternsToCompare)
- {
- for (int i = 0; i < mostRecentPatternsToCompare; i++)
- {
- if (monoHistory[start + i] != monoHistory[monoHistory.Count - mostRecentPatternsToCompare + i])
- return false;
- }
-
- return true;
- }
-
- ///
- /// Calculates the strain penalty for a colour pattern repetition.
- ///
- /// The number of notes since the last repetition of the pattern.
- private double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince);
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs
index cabfd231d8..4b8a8033a4 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs
@@ -1,8 +1,10 @@
// Copyright (c) ppy Pty Ltd . 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.Taiko.Difficulty.Evaluators;
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
{
@@ -11,6 +13,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
///
public class SingleKeyStamina
{
+ private const double StrainDecayBase = 0.4;
+
+ private double CurrentStrain = 0;
+
private double? previousHitTime;
///
@@ -24,19 +30,23 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
return 0;
}
- double objectStrain = 0.5;
- objectStrain += speedBonus(current.StartTime - previousHitTime.Value);
+ // CurrentStrain += strainDecay(current.StartTime - current.Previous(0).StartTime);
+ // CurrentStrain += 0.5 + 0.5 * strainDecay(current.StartTime - current.Previous(0).StartTime);
+ CurrentStrain += 1;
+ CurrentStrain *= ColourEvaluator.EvaluateDifficultyOf(current) * 0.1 + 0.9;
+ CurrentStrain *= strainDecay(current.StartTime - previousHitTime.Value);
previousHitTime = current.StartTime;
- return objectStrain;
+ return CurrentStrain;
}
///
/// Applies a speed bonus dependent on the time since the last hit performed using this key.
///
/// The duration between the current and previous note hit using the same key.
- private double speedBonus(double notePairDuration)
+ private double strainDecay(double notePairDuration)
{
- return 175 / (notePairDuration + 100);
+ return Math.Pow(StrainDecayBase, notePairDuration / 1000);
+ // return 175 / (notePairDuration + 100);
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
index 61bcbfa59d..7196b68df2 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
@@ -5,6 +5,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
@@ -17,8 +18,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
///
public class Stamina : StrainDecaySkill
{
- protected override double SkillMultiplier => 1;
- protected override double StrainDecayBase => 0.4;
+ protected override double SkillMultiplier => 3.6;
+ protected override double StrainDecayBase => 0;
private readonly SingleKeyStamina[] centreKeyStamina =
{
@@ -76,7 +77,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
}
TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current;
- return getNextSingleKeyStamina(hitObject).StrainValueOf(hitObject);
+ double objectStrain = getNextSingleKeyStamina(hitObject).StrainValueOf(hitObject);
+ // objectStrain *= ColourEvaluator.EvaluateDifficultyOf(current) * 0.3 + 0.7;
+ return objectStrain;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 4dd3b0b8cc..423903db2f 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoDifficultyCalculator : DifficultyCalculator
{
- private const double rhythm_skill_multiplier = 0.014;
- private const double colour_skill_multiplier = 0.01;
+ private const double rhythm_skill_multiplier = 0.017;
+ private const double colour_skill_multiplier = 0.028;
private const double stamina_skill_multiplier = 0.021;
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
// Find repetition interval for the final TaikoDifficultyHitObjectColour
// TODO: Might be a good idea to refactor this
- ((TaikoDifficultyHitObject)difficultyHitObject.Last()).Colour.FindRepetitionInterval();
+ ((TaikoDifficultyHitObject)difficultyHitObject.Last()).Colour?.FindRepetitionInterval();
return difficultyHitObject;
}
@@ -76,18 +76,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier;
double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier;
- double staminaPenalty = simpleColourPenalty(staminaRating, colourRating);
- staminaRating *= staminaPenalty;
+ // double staminaPenalty = simpleColourPenalty(staminaRating, colourRating);
+ // staminaRating *= staminaPenalty;
//TODO : This is a temporary fix for the stamina rating of converts, due to their low colour variance.
- if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0 && colourRating < 0.05)
- {
- staminaPenalty *= 0.25;
- }
+ // if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0 && colourRating < 0.05)
+ // {
+ // staminaPenalty *= 0.25;
+ // }
- double combinedRating = locallyCombinedDifficulty(colour, rhythm, stamina, staminaPenalty);
+ double combinedRating = locallyCombinedDifficulty(colour, rhythm, stamina);
double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating);
- double starRating = 1.4 * separatedRating + 0.5 * combinedRating;
+ double starRating = 1.9 * combinedRating;
starRating = rescale(starRating);
HitWindows hitWindows = new TaikoHitWindows();
@@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
/// For each section, the peak strains of all separate skills are combined into a single peak strain for the section.
/// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
///
- private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina stamina, double staminaPenalty)
+ private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina stamina)
{
List peaks = new List();
@@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier;
- double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier * staminaPenalty;
+ double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier;
double peak = norm(2, colourPeak, rhythmPeak, staminaPeak);