From 48b7e0f0900d52a23bb9fa1a753983c373ca6ec8 Mon Sep 17 00:00:00 2001 From: Jay L Date: Tue, 10 May 2022 18:17:40 +1000 Subject: [PATCH] Stamina Skill Rewrite and Refactor --- .../Difficulty/Skills/Stamina.cs | 139 +++++++++--------- .../Difficulty/TaikoDifficultyCalculator.cs | 20 +-- 2 files changed, 75 insertions(+), 84 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 54cf233d69..b10da95444 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -1,16 +1,50 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; +using System; 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.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { + class SingleKeyStamina + { + private double previousHitTime = -1; + + private double strainValueOf(DifficultyHitObject current) + { + if (previousHitTime == -1) + { + previousHitTime = current.StartTime; + return 0; + } + else + { + double objectStrain = 0.5; + objectStrain += speedBonus(current.StartTime - previousHitTime); + previousHitTime = current.StartTime; + return objectStrain; + } + } + + public double StrainValueAt(DifficultyHitObject current) + { + return strainValueOf(current); + } + + /// + /// 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) + { + return 175 / Math.Pow(notePairDuration + 100, 1); + } + } + /// /// Calculates the stamina coefficient of taiko difficulty. /// @@ -22,39 +56,43 @@ 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 max_history_length = 2; + private SingleKeyStamina[] keyStamina = new SingleKeyStamina[4] + { + new SingleKeyStamina(), + new SingleKeyStamina(), + new SingleKeyStamina(), + new SingleKeyStamina() + }; - /// - /// The index of the hand this instance is associated with. - /// - /// - /// The value of 0 indicates the left hand (full alternating gameplay starting with left hand is assumed). - /// This naturally translates onto index offsets of the objects in the map. - /// - private readonly int hand; - - /// - /// Stores the last durations between notes hit with the hand indicated by . - /// - private readonly LimitedCapacityQueue notePairDurationHistory = new LimitedCapacityQueue(max_history_length); - - /// - /// Stores the of the last object that was hit by the other hand. - /// - private double offhandObjectDuration = double.MaxValue; + private int donIndex = 1; + private int katIndex = 3; /// /// Creates a skill. /// /// Mods for use in skill calculations. - /// Whether this instance is performing calculations for the right hand. - public Stamina(Mod[] mods, bool rightHand) + public Stamina(Mod[] mods) : base(mods) { - hand = rightHand ? 1 : 0; + } + + private SingleKeyStamina getNextSingleKeyStamina(TaikoDifficultyHitObject current) + { + if (current.HitType == HitType.Centre) + { + donIndex = donIndex == 0 ? 1 : 0; + return keyStamina[donIndex]; + } + else + { + katIndex = katIndex == 2 ? 3 : 2; + return keyStamina[katIndex]; + } + } + + private double sigmoid(double val, double center, double width) + { + return Math.Tanh(Math.E * -(val - center) / width); } protected override double StrainValueOf(DifficultyHitObject current) @@ -65,52 +103,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills } TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; + double objectStrain = getNextSingleKeyStamina(hitObject).StrainValueAt(hitObject); - if (hitObject.ObjectIndex % 2 == hand) - { - double objectStrain = 1; - - if (hitObject.ObjectIndex == 1) - return 1; - - notePairDurationHistory.Enqueue(hitObject.DeltaTime + offhandObjectDuration); - - double shortestRecentNote = notePairDurationHistory.Min(); - objectStrain += speedBonus(shortestRecentNote); - - if (hitObject.StaminaCheese) - objectStrain *= cheesePenalty(hitObject.DeltaTime + offhandObjectDuration); - - return objectStrain; - } - - offhandObjectDuration = hitObject.DeltaTime; - return 0; - } - - /// - /// Applies a penalty for hit objects marked with . - /// - /// The duration between the current and previous note hit using the hand indicated by . - private double cheesePenalty(double notePairDuration) - { - if (notePairDuration > 125) return 1; - if (notePairDuration < 100) return 0.6; - - return 0.6 + (notePairDuration - 100) * 0.016; - } - - /// - /// Applies a speed bonus dependent on the time since the last hit performed using this hand. - /// - /// The duration between the current and previous note hit using the hand indicated by . - private double speedBonus(double notePairDuration) - { - if (notePairDuration >= 200) return 0; - - double bonus = 200 - notePairDuration; - bonus *= bonus; - return bonus / 100000; + return objectStrain; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index a9d512f076..de69f63bf4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { private const double rhythm_skill_multiplier = 0.014; private const double colour_skill_multiplier = 0.01; - private const double stamina_skill_multiplier = 0.02; + private const double stamina_skill_multiplier = 0.021; public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -33,8 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { new Colour(mods), new Rhythm(mods), - new Stamina(mods, true), - new Stamina(mods, false), + new Stamina(mods) }; protected override Mod[] DifficultyAdjustmentMods => new Mod[] @@ -58,7 +57,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty ); } - new StaminaCheeseDetector(taikoDifficultyHitObjects).FindCheese(); return taikoDifficultyHitObjects; } @@ -69,17 +67,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty var colour = (Colour)skills[0]; var rhythm = (Rhythm)skills[1]; - var staminaRight = (Stamina)skills[2]; - var staminaLeft = (Stamina)skills[3]; + var stamina = (Stamina)skills[2]; double colourRating = colour.DifficultyValue() * colour_skill_multiplier; double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier; - double staminaRating = (staminaRight.DifficultyValue() + staminaLeft.DifficultyValue()) * stamina_skill_multiplier; + double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier; double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); staminaRating *= staminaPenalty; - double combinedRating = locallyCombinedDifficulty(colour, rhythm, staminaRight, staminaLeft, staminaPenalty); + double combinedRating = locallyCombinedDifficulty(colour, rhythm, stamina, staminaPenalty); double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating); double starRating = 1.4 * separatedRating + 0.5 * combinedRating; starRating = rescale(starRating); @@ -127,20 +124,19 @@ 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 staminaRight, Stamina staminaLeft, double staminaPenalty) + private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina stamina, double staminaPenalty) { List peaks = new List(); var colourPeaks = colour.GetCurrentStrainPeaks().ToList(); var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList(); - var staminaRightPeaks = staminaRight.GetCurrentStrainPeaks().ToList(); - var staminaLeftPeaks = staminaLeft.GetCurrentStrainPeaks().ToList(); + var staminaPeaks = stamina.GetCurrentStrainPeaks().ToList(); for (int i = 0; i < colourPeaks.Count; i++) { double colourPeak = colourPeaks[i] * colour_skill_multiplier; double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier; - double staminaPeak = (staminaRightPeaks[i] + staminaLeftPeaks[i]) * stamina_skill_multiplier * staminaPenalty; + double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier * staminaPenalty; double peak = norm(2, colourPeak, rhythmPeak, staminaPeak);