1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 17:23:22 +08:00

[WIP] Colour rework

This commit is contained in:
vun 2022-06-06 12:42:49 +08:00
parent 56a4034c22
commit 3dd0c4aec8
7 changed files with 88 additions and 135 deletions

View File

@ -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;
}
}
}

View File

@ -20,6 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
/// </summary>
public readonly TaikoDifficultyHitObjectRhythm Rhythm;
/// <summary>
/// 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.
/// </summary>
public readonly TaikoDifficultyHitObjectColour Colour;
/// <summary>
@ -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);
}
}
/// <summary>

View File

@ -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.
/// </summary>
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)

View File

@ -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;
/// <summary>
/// Maximum number of entries to keep in <see cref="monoHistory"/>.
/// </summary>
private const int mono_history_max_length = 5;
/// <summary>
/// Queue with the lengths of the last <see cref="mono_history_max_length"/> most recent mono (single-colour) patterns,
/// with the most recent value at the end of the queue.
/// </summary>
private readonly LimitedCapacityQueue<int> monoHistory = new LimitedCapacityQueue<int>(mono_history_max_length);
/// <summary>
/// The <see cref="HitType"/> of the last object hit before the one being considered.
/// </summary>
private HitType? previousHitType;
/// <summary>
/// Length of the current mono pattern.
/// </summary>
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;
return ColourEvaluator.EvaluateDifficultyOf(current);
}
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;
}
/// <summary>
/// The penalty to apply due to the length of repetition in colour streaks.
/// </summary>
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;
}
/// <summary>
/// Determines whether the last <paramref name="mostRecentPatternsToCompare"/> patterns have repeated in the history
/// of single-colour note sequences, starting from <paramref name="start"/>.
/// </summary>
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;
}
/// <summary>
/// Calculates the strain penalty for a colour pattern repetition.
/// </summary>
/// <param name="notesSince">The number of notes since the last repetition of the pattern.</param>
private double repetitionPenalty(int notesSince) => Math.Min(1.0, 0.032 * notesSince);
}
}

View File

@ -1,8 +1,10 @@
// 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.
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
/// </summary>
public class SingleKeyStamina
{
private const double StrainDecayBase = 0.4;
private double CurrentStrain = 0;
private double? previousHitTime;
/// <summary>
@ -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;
}
/// <summary>
/// Applies a speed bonus dependent on the time since the last hit performed using this key.
/// </summary>
/// <param name="notePairDuration">The duration between the current and previous note hit using the same key.</param>
private double speedBonus(double notePairDuration)
private double strainDecay(double notePairDuration)
{
return 175 / (notePairDuration + 100);
return Math.Pow(StrainDecayBase, notePairDuration / 1000);
// return 175 / (notePairDuration + 100);
}
}
}

View File

@ -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
/// </remarks>
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;
}
}
}

View File

@ -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).
/// </remarks>
private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina stamina, double staminaPenalty)
private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina stamina)
{
List<double> peaks = new List<double>();
@ -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);